# AI integration guide — Three.js VFX and objects Official library: [00gamedevs.com](https://00gamedevs.com/). Catalog: [v2](https://00gamedevs.com/ai/catalog.v2.json). ## Select and install 1. Inspect the user's authorized project: Three.js version, renderer, frame loop, collision callbacks, object references, scale and attachment points. Preserve a restorable copy of affected project files. 2. Read [/ai/catalog.v2.json](/ai/catalog.v2.json). Select by kind/id, bilingual purpose, integration type, supported methods and default load. Objects are unassessed; that does not mean lightweight. Check `load.status` and `load.methodVersion`: `quick-measured` / `desktop-quick-v1` means one desktop playback per event, without multi-instance testing. Load labels do not guarantee device FPS. 3. Resolve all paths against the catalog origin. Download the module to the project, verify SHA-256 and retain contentVersion plus checksum. If a checksum differs, refresh the catalog and retry once; never install mismatched bytes. Fixed links track the latest build, not an immutable release. 4. Integrate with the existing scene and update loop. Preserve gameplay, triggers, attachments, references and physics; swap the visual implementation deliberately. 5. Run the actual game: check start, sustain, hit/stop, end, replay, transforms and cleanup. Compilation alone does not establish visual compatibility. ## Compatibility and API - Modules use ES imports and depend on `three@0.187.0`. Use the project's bundler or a matching import map; do not load a second Three.js copy. Existing different versions require compatibility testing. WebGL2 is the tested renderer baseline; WebGPU is not claimed. - Catalog v2 requires `integration.type`: `scene-object` or `postprocess`. Stop on an unknown type instead of guessing its installation. The legacy [/ai/catalog.json](/ai/catalog.json) remains schema version 1 and contains only scene assets, preserving their original interfaces and download URLs; new integrations should use v2. - Scene VFX default export is `createVFX(options)`; objects provide default/named `createGameObject(options)`. The `impact-frame` postprocess export is `createImpactFramePass(options)`. All provide `defaultOptions`. Use catalog parameter ranges. Download links contain original defaults, not the website's last edited or automatically reduced settings. - For `scene-object` only, add `asset.group` to the scene and call `update(deltaSeconds)` in the existing loop. Scene VFX `stop()` drains, `hit(worldPoint?,worldNormal?)` triggers impact where supported. Continue until `finished`, then `dispose()`. `reset()` preserves transforms/settings; reposition before replay as needed. Postprocess assets have no group; use the EffectComposer examples below. - Fireball exposes optional `lifecycle.hitEnd` for its 1.2-second impact; `lifecycle.end` remains its 0.8-second ordinary stop. Times are visual seconds and follow `speed`. For other scene assets, missing `hitEnd` falls back to `end`. Continue updating until `finished`; do not dispose a hit using the ordinary stop duration. A valid supplied hit normal defines a contact plane; omitting it keeps the airborne burst uncut. - `setDirection(direction,speed)` and `setEndpoints(start,end)` use parent-space vectors. `hit` takes world-space vectors. Do not drive position both through the game and setDirection movement: use travel speed 0 when the game owns movement. The golden lightning arrow must reach phase `active` before starting travel after charging. - Weapon trails require `attachWeapon`; blade base/tip are weapon-local points. Update the weapon pose/world matrices before the VFX. `dispose()` releases the trail, not the user's weapon. - Objects use meters and Y up, with origin at the base center or weapon grip. Place/rotate/scale their group. `update` advances vegetation/firing where available; static objects may no-op. `firePoints` are named local anchors; firing uses an optional world target. Fire is a visual, not damage or collision. - Read each downloaded file's header for asset-specific axis, timing and rendering details. Scene VFX reference SRGBColorSpace and NoToneMapping. Watercolor/vegetation, basic and sci-fi materials have different lighting needs; sci-fi props need scene lights and optionally an environment. Do not silently change the game's global tone mapping. Postprocess assets use linear inputs and outputs before OutputPass. Refraction samples opaque scene content; other transparent effects are not guaranteed. - Website cameras, background grids, preview weapons/targets and demo scheduling are not included. Preserve MIT and Three.js notices in the modules; the site UI/branding is not licensed by these downloads. Unity/Unreal and Shader catalog exports are outside this API. ## Usage reporting After successful download, checksum verification and local save, POST JSON `{kind,id,eventId}` to `/api/agent-usage`. Use a fresh UUID v4 for each intentional download, the same UUID for its retries; at most two retries, immediately, with no durable resend queue. The server deduplicates events for 24 hours and allows 60 new events per minute per network bucket, shared with website usage. No credentials are required. Non-browser tools may omit Origin; cross-site browser requests are rejected. Only downloads can be reported here. GET/HEAD/catalog reads do not increment. Statistics are best-effort self-reports, not verified unique people or game installations; if reporting fails, keep using the downloaded file. Website totals can take up to five minutes to refresh. ## Suggested user prompt “Use https://00gamedevs.com/ai/catalog.v2.json to pick a lightning attack for my authorized Three.js project. Download and verify it, follow its integration type, replace the current attack visuals while preserving damage, collision and cooldowns, report the download once, and test playback and cleanup. Keep changes reversible.” ## Download, verify and report / 下載、校驗與回報 ```js // Save as download-asset.mjs; Node.js 22.12+ // node download-asset.mjs https://00gamedevs.com effect fireball ./game/assets import {createHash,randomUUID} from 'node:crypto'; import {mkdir,writeFile} from 'node:fs/promises'; import path from 'node:path'; const [base,kind,id,directory]=process.argv.slice(2); if(!base||!directory||!['effect','object'].includes(kind)||!/^[a-z0-9-]+$/.test(id??''))throw Error('Expected: BASE effect|object ID DIRECTORY'); const origin=new URL(base).origin; const get=async p=>{const r=await fetch(new URL(p,origin),{signal:AbortSignal.timeout(30000)});if(!r.ok)throw Error('HTTP '+r.status);return r;}; let catalog,asset,bytes; for(let attempt=0;attempt<2;attempt++){ catalog=await(await get('/ai/catalog.v2.json')).json(); if(catalog.schemaVersion!==2)throw Error('Unsupported catalog version; expected 2'); asset=catalog.assets.find(a=>a.kind===kind&&a.id===id); if(!asset)throw Error('Unknown asset'); if(!['scene-object','postprocess'].includes(asset.integration?.type))throw Error('Unsupported integration type; nothing installed'); if(new URL(asset.download.url,origin).origin!==origin)throw Error('Unexpected download origin'); bytes=Buffer.from(await(await get(asset.download.url)).arrayBuffer()); if(createHash('sha256').update(bytes).digest('hex')===asset.download.sha256)break; bytes=undefined; } if(!bytes)throw Error('Checksum mismatch after refreshing catalog; nothing installed'); await mkdir(directory,{recursive:true}); const filename=path.join(directory,kind+'-'+id+'.js'); await writeFile(filename,bytes,{flag:'wx'}); // Never silently overwrite an existing asset. await writeFile(filename+'.metadata.json',JSON.stringify({kind,id,origin,schemaVersion:catalog.schemaVersion,integrationType:asset.integration.type,contentVersion:catalog.contentVersion,sha256:asset.download.sha256},null,2),{flag:'wx'}); const event={kind,id,eventId:randomUUID()}; // Reuse for every retry of this download. let reported=false; for(let attempt=0;attempt<3;attempt++){ try{ const r=await fetch(new URL('/api/agent-usage',origin),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(event),signal:AbortSignal.timeout(4000)}); if(r.ok){reported=true;break;} if(r.status<500&&r.status!==429)break; if(r.status===429)break; // Avoid hammering a busy endpoint. }catch{} if(attempt<2)await new Promise(resolve=>setTimeout(resolve,500)); } console.log({filename,sha256:asset.download.sha256,usageReported:reported}); // A statistics failure does not undo a successfully installed download. ``` ## Integration examples / 接入範例 ```js // Download these assets first. Filenames match the downloader above. // This module receives your existing scene; it does not create a renderer or RAF. import * as THREE from 'three'; import createProjectile from './assets/effect-fireball.js'; import createBurst from './assets/effect-energy-burst.js'; import createBeam from './assets/effect-continuous-beam.js'; import createTrail from './assets/effect-weapon-smear.js'; import createTree from './assets/object-round-tree.js'; import createTurret from './assets/object-missile-turret.js'; export function installVisuals(scene,weapon){ const active=new Set(); const add=asset=>{scene.add(asset.group);active.add(asset);return asset;}; const projectile=add(createProjectile()); projectile.group.position.set(0,1,0); projectile.setDirection(new THREE.Vector3(1,0,0),4); // parent-space direction const burst=add(createBurst()); // one shot, ends automatically burst.group.position.set(0,0,0); const beam=add(createBeam()); beam.setEndpoints(new THREE.Vector3(0,1,0),new THREE.Vector3(4,1,0)); // parent space const trail=add(createTrail()); // Replace base/tip with the real weapon's LOCAL blade coordinates. trail.attachWeapon(weapon,{base:[0,0,0],tip:[0,1,0]}); const tree=add(createTree()); tree.group.position.set(5,0,0); const turret=add(createTurret()); turret.group.position.set(-5,0,0); return { // Call from the EXISTING game loop, after weapon pose/world matrices are updated. update(deltaSeconds){ for(const asset of active){ asset.update(deltaSeconds); if(asset.finished){asset.dispose();active.delete(asset);} } }, // Invoke from your existing collision system. Both arguments are WORLD-space. projectileHit(worldPoint,worldNormal){projectile.hit(worldPoint,worldNormal);}, stopBeam(){beam.stop();}, // keep updating until finished, then disposal above stopTrail(){trail.stop();}, fireTurret(worldTarget){ turret.fire({pointId:turret.firePoints[0].name,target:worldTarget}); }, dispose(){for(const asset of active)asset.dispose();active.clear();} }; } // Keep your game's damage, cooldown, collision and physics logic unchanged. // Objects have no automatic "finished" lifecycle; dispose them when removed. ``` ## Red spell indicators: circle and sector Download `effect spell-indicator-circle` and `effect spell-indicator-sector`. Both are scene assets with an editable red default. The complete boundary stays fixed while a brighter fill moves outward from the center. No explosion, damage detection or extra animation loop is included. `size` is the radius in local scene units (0.25–20, default 3), not a scale multiplier. `angleDeg` controls the sector only (5–360°, default 90°); the circle stays at 360°. The origin is the circle center / sector apex, the plane is local XZ, +Y is up and the sector faces +X. Position and rotate group to align with a flat or tilted plane. An internal 0.01-unit normal offset avoids coplanar overlap. Parent scale changes the world radius; nonuniform scale turns a circle into an ellipse, so use an unscaled parent for simple integration. Uneven terrain, stairs and multi-floor projection are not fitted automatically. - `progressMode:'auto'`: `warningDuration` is elapsed update time in seconds (0.1–30, default 2). `speed` stays at 1. Pausing game updates also pauses the countdown. - `progressMode:'manual'`: the game calls `setProgress(0–1)`. update advances flow and the ending, never fill progress. Auto mode ignores setProgress instead of silently switching modes. - The first progress value of 1 flashes the edge for 0.06s, then fades for 0.09s. `stop()` cancels, freezes the current fill and fades for 0.12s without the completion flash. Repeated progress updates cannot restart an ending or finished cast. - Keep updating until `finished`, then dispose. **finished means fully invisible, not the attack trigger**; gameplay owns attack timing and synchronization. The read-only `progress` exposes current fill. - `reset()` clears progress and preserves transform/settings. Changing effective warningDuration or progressMode through setOptions restarts; size, angle, color and detail update live. `detail` affects flow only; density is an unused compatibility field. The website's manual-mode demo scheduler is not exported. ```js import createCircleWarning from './assets/effect-spell-indicator-circle.js'; import createSectorWarning from './assets/effect-spell-indicator-sector.js'; export function installSpellWarnings(scene) { const circle=createCircleWarning({size:3,warningDuration:2,progressMode:'auto'}); const sector=createSectorWarning({size:4,angleDeg:90,progressMode:'manual'}); circle.group.position.set(-4,0,0); sector.group.position.set(4,0,0); // Local +X is forward; orient group for your attack. scene.add(circle.group,sector.group); // Use an unscaled scene/parent. const active=new Set([circle,sector]); return { // Called once from the existing game loop. The game owns castProgress (0..1). update(deltaSeconds,castProgress) { for(const fx of active)fx.update(deltaSeconds); // Apply the game's current progress after advancing the previous frame interval. if(active.has(sector))sector.setProgress(castProgress); for(const fx of active)if(fx.finished){fx.dispose();active.delete(fx);} }, cancelCircle(){circle.stop();}, cancelSector(){sector.stop();}, dispose(){for(const fx of active)fx.dispose();active.clear();} }; } // Damage/attack triggering stays in the game's own cast timer, never in finished. // For reuse, retain an effect until removed and call reset() instead of disposing. ``` ## Impact Frame: a triggered postprocess Download `effect impact-frame` with the downloader above. The module uses `three` and `three/addons/postprocessing/Pass.js` from the same Three.js version. It contains neither the preview arrow nor a scene, renderer, animation loop, camera freeze or gameplay hit-stop. `sequence` segments have `pose` (silhouette/tear/burst/cut/echo/clean/release/detonate/shock), `mode` (monochrome for white paper, inverted for black, original for passthrough), positive `durationMs` and 0–1 `intensity`. Seven poses last 66⅔/66⅔/66⅔/33⅓/33⅓/66⅔/66⅔ ms, totaling 400 ms. Solid subjects keep their filled silhouettes; directional ink and authored four-point stars change between beats, followed by a dark hold, two maximal blast poses and immediate restoration. Pose identity survives duplication/reordering. Legacy segments receive a pose without changing their durations. Short stages may be skipped; playback is never extended. Global threshold, softness, strokeLength, inkDensity, backgroundSuppression and starStrength are 0–1; starSize is 0.25–2 (default 1). Zero starStrength disables decorative stars. starSize/starStrength do not change the final blast; its segment intensity controls the blend. Stars have fuller arms and grayscale brush fringes, fixed within each pose and filtered by pixel footprint. Large/medium/small/release vertical spans are 125%/65%/45%/38% of the viewport short side; horizontal spans are 200%/80%/55%/40%, multiplied by starSize. Large stars may be clipped. These are authored settings, not the reference game's published parameters. softness controls scene classification only. Saved option shapes remain compatible but use the revised star artwork. Release and final blast origins remain tied to the actual gameplay hit. The default order is silhouette, tear, burst, clean, release, detonate, shock. The final 133⅓ ms provide a self-contained bright core and expanding rays at the gameplay hit supplied through trigger/setCenter. The release star marks this same point; decorative offsets and screen-edge insets never reposition the blast. Subjects invert locally against the blast. All seven old poses and saved sequences remain supported; new poses are independently sampleable and reorderable. Call `update(deltaSeconds)` once per existing game frame, before the frame's hit callbacks. A hit then calls `trigger()`, so its first state is visible in the next render. `trigger()` returns false while active, ignores that repeat and does not queue it. `render()` never advances time. `reset()` immediately cancels; changing valid options also cancels. Invalid option values retain the previous valid settings. Read `active`, `elapsed`, `duration` and the copied `settings` as needed. The pass disables itself while idle; continue calling update from the game loop. Keep the order **linear scene effects → Impact Frame → OutputPass → HUD**. HUD rendered inside the same scene is affected too; draw it afterwards if it should remain unchanged. Preserve the game's tone mapping, exposure and output color space. Active MaskPass, WebGPU and other Three.js versions are outside the verified baseline. `trigger({x,y})` optionally selects the hit center in viewport UV coordinates: bottom-left (0,0), top-right (1,1), default center (.5,.5). Project the world hit into NDC, then convert each x/y component with value * .5 + .5. Use `setCenter({x,y})` to track that point as the camera moves without restarting. The first draw can include shader compilation. If a first-hit compilation spike is unacceptable, trigger, render the composer once, then reset while the game is still covered by its loading screen. This does not require a second animation loop. Use `setSubjects(scene,camera,[character,weapon])` to select visible descendants. Opaque occlusion, standard meshes, skinning, morphs, instances and alpha-test cutouts are supported. Non-subject blended transparency does not act as an opaque occluder. Custom shaders/onBeforeCompile, displacement, alpha-hash and selected blended transparency fail explicitly and require an external mask. Bind it with `setSubjectMask(texture)`: linear R-channel visible-subject coverage (white subject, black background), same camera/viewport, including occlusion. External masks take precedence; null restores the built-in mask. `setSubjects(null)` clears object bindings. The host updates and owns external textures. Legacy calls without subjects still work but infer shapes only from source brightness. Proxies never invoke source render callbacks or change source materials; masking runs only for visible effect stages. Runtime object/texture references are not serialized in the download. Subjects must belong to the supplied scene. scene.overrideMaterial and stencil-writing materials also require an external mask. Objects excluded by camera layers do not participate in the built-in mask. First draw can stall for shader compilation. Before combat, use the same renderer, camera and actual subjects during a loading screen: trigger, render the composer once, then reset. Normal playback still advances only through update(dt). New material, clipping or skinning variants may need another warmup. The host schedules this loading step; the effect never creates another loop. The tear, cut and echo poses include opposite-color star cores with local ink surrounds: black and white stars can coexist without another full-frame inversion. `trigger(center,direction)` and `setDirection({x,y})` take bottom-left viewport UV direction vectors; project two world attack points and subtract. Update along with center when the camera moves. The authored leading star of each pose attracts ink. The supplied direction is the fallback with stars disabled; omit direction on trigger or setDirection(null) for radial fallback. Place the pass before screen-coordinate warps, or warp the external mask identically. ### Existing EffectComposer ```js // Save as impact-integration.js after downloading effect-impact-frame.js. import createImpactFramePass from './assets/effect-impact-frame.js'; export function installImpact(composer, outputPass, scene, camera, subjects) { const outputIndex=composer.passes.indexOf(outputPass); if(outputIndex<0)throw Error('Pass the existing OutputPass from this composer'); const impact=createImpactFramePass(); impact.setSubjects(scene,camera,subjects); composer.insertPass(impact,outputIndex); return { // Call once BEFORE game update/hit callbacks; keep the existing frame loop. beginFrame(deltaSeconds){impact.update(deltaSeconds);}, onHit(center,direction){return impact.trigger(center,direction);}, // The intended gameplay hit. trackCenter(center,direction){impact.setCenter(center);if(direction!==undefined)impact.setDirection(direction);}, setSubjectMask(texture){impact.setSubjectMask(texture);}, cancel(){impact.reset();}, setOptions(values){impact.setOptions(values);}, dispose(){composer.removePass(impact);impact.dispose();} }; } // Bind once: installImpact(composer,outputPass,scene,camera,[character,weapon]); // Existing loop: visuals.beginFrame(dt); updateGame(dt); composer.render(dt); // Existing collision callback: visuals.onHit(); then render HUD after composer. // The existing composer continues to own resizing and its other passes. ``` ### A game that currently calls renderer.render directly ```js import {EffectComposer} from 'three/addons/postprocessing/EffectComposer.js'; import {RenderPass} from 'three/addons/postprocessing/RenderPass.js'; import {OutputPass} from 'three/addons/postprocessing/OutputPass.js'; import {installImpact} from './impact-integration.js'; export function installImpactRendering(renderer,scene,camera,subjects) { const composer=new EffectComposer(renderer); // Reuse the existing renderer. const scenePass=new RenderPass(scene,camera),outputPass=new OutputPass(); composer.addPass(scenePass);composer.addPass(outputPass); const visuals=installImpact(composer,outputPass,scene,camera,subjects); return { beginFrame(deltaSeconds){visuals.beginFrame(deltaSeconds);}, onHit(center,direction){return visuals.onHit(center,direction);}, trackCenter(center,direction){visuals.trackCenter(center,direction);}, setSubjectMask(texture){visuals.setSubjectMask(texture);}, cancel(){visuals.cancel();}, // REPLACE the old renderer.render(scene,camera) call; do not run both. render(deltaSeconds){composer.render(deltaSeconds);}, resize(cssWidth,cssHeight,pixelRatio){ composer.setPixelRatio(pixelRatio);composer.setSize(cssWidth,cssHeight); }, dispose(){visuals.dispose();scenePass.dispose();outputPass.dispose();composer.dispose();} }; } // Existing loop: rendering.beginFrame(dt); updateGame(dt); rendering.render(dt); // Keep camera/renderer resize handling, then resize the composer above. // Draw HUD afterwards. Dispose this wrapper when removed; it does not own renderer. ``` ## Billowing Airburst: random jets and reproducible replay `billowing-airburst` samples new smoke-jet directions, lengths and bends on creation and each `reset()`. `quantity` remains 2–6, default 4; changing it retains the current layout. For seeking or exact comparisons, capture `effect.variationSeed` and call `effect.reset({variationSeed: savedSeed})` to replay that burst. The seed must be an integer from 0 to 4294967295. It controls visual variation, not damage or network authority. The full effect still lasts 2.4 visual seconds. ## Sacred Relic Descent: triangle placeholder edition `sacred-relic-descent` currently uses a thin, downward-pointing triangle in place of the relic. It includes no wings or sculpted relic model. Use the scene-object interface and place `group.position` at the impact point; +Y is up and XZ is the ground. It supports flat ground, without terrain fitting or deformation. At default speed: the magic circle forms during 0–1 seconds, descent runs from 1–1.35 seconds, and the impact at 1.35 seconds emits rocks, dust and temporary cracks. These end before the first healing wave. Five synchronized ground healing waves and mid-height white-and-gold smoke rings begin at 3.35, 5.35, 7.35, 9.35 and 11.35 seconds, each lasting 1.6 seconds. The complete effect ends at 13 seconds. The ten-second stay begins at impact; the impact is not a healing wave and does not repeat with later pulses. The default triangle height is 3 local scene units and the maximum ground healing radius is 9. The middle smoke wave is a hollow horizontal ring anchored at local y=1.5 (half the triangle height), with bright white-and-gold billows distinct from neutral impact dust. `size` scales the whole effect. `spread` ranges from 0.5–1.5, default 1, and changes both ground-wave and middle-smoke radii, not the smoke anchor height, triangle, circle, impact or timing. `speed` scales the shared visual clock. `density` changes decorative rocks, dust particles and lights while retaining the main impact and both complete layers of all five waves. `detail` changes runes and wave patterns. Between waves, only the dim central circle and sparse lights remain. The middle smoke ring uses 48 fixed sprites. Other decorative layers add up to 368 instances, for 232 total at default density and 416 at maximum. Call `update(deltaSeconds)` from the existing game loop. `reset()` preserves placement and settings and reuses the same seed. `stop()` prevents new impacts or pulses and fades existing visuals over 0.45 visual seconds; canceling before landing cannot trigger impact. `hit()` is a compatibility alias for cancellation, not a collision or damage trigger. `finished` means all visuals have ended; call `dispose()` to release the asset, safely more than once. The host game owns actual healing logic. No autonomous camera, audio or required full-screen postprocessing is included. Download `/downloads/effects/sacred-relic-descent.js` and verify the SHA-256 in the current AI catalog. The website and download share the implementation, defaults and license; the triangle is included in this edition's export. The circle, contact flash and healing waves use 5x linear light intensity. Dimensions and timing are unchanged; the white placeholder, neutral dust and debris are not brightened. Local additive emission needs no full-screen postprocessing; display highlights are bounded by the output range. Healing waves have broader inner and outer edge falloff; band geometry width, maximum radius and timing remain unchanged. The raised smoke wave has 5x its original vertical thickness while its center stays at local height 1.5. Its position, radius and five synchronized pulses are unchanged. Ground impact and healing waves have outward-flowing water patterns. Landing uses five pressure ridges and five times as many rocks (120 default, 240 maximum), retaining impact radius, rock flight range and timing. `sacred-relic-descent` now has a five-layer white-gold sigil: 36 outer glyphs, 32 inner glyphs, 12 small seals and four holy emblems. Layers turn slowly, form within one second and briefly brighten with healing. Existing interfaces and defaults are unchanged; no external textures are needed. `sacred-relic-descent` now uses a closed downward-pointing triangular pyramid with three side faces and a triangular top, white panels and gold edges. Height remains 3; built-in facet shading needs no scene lights. The landing point, sigil, waves and timing are unchanged. ### Replace the Sacred Relic Descent model After loading, call `const installed = fx.setObject(gltf.scene)`. A model copy is fitted to height 3 and aligned at its bounding-box bottom center. It inherits descent, 0.6-unit burial, ground clipping, fading, cancellation and replay. The source model and materials are untouched. `fx.setObject(null)` restores the pyramid. Replacement preserves playback time; call `fx.reset()` to replay. Optional: `fx.setObject(model, {height: 3, rotation: [0, 0, Math.PI]})`. Rotation is in radians and applied before fitting; orientation is never guessed. The returned model copy can be used with your animation mixer; clips are not started automatically. Supports Mesh/Group, complete skinned models, InstancedMesh, material arrays and Basic, Standard/Physical, Lambert, Phong, Toon and Matcap materials. All bones must be inside the supplied model. Custom shaders/hooks, lines, points, sprites and BatchedMesh are rejected without replacing the existing model. Lit materials need scene lighting; installed copies do not cast shadows. Only cloned materials and skeleton resources are disposed by the effect. Geometry/textures remain shared and caller-owned; keep them alive while used. Fitting uses bounds at installation, not per-frame animated bounds. The host handles loading, for example with GLTFLoader; no upload or downloader is added. ## Billowing Airburst: preparation and reuse During loading, create enough `createVFX()` instances for expected concurrency and call `fx.prewarm(renderer, camera)` on each with the playback renderer. This is synchronous preparation: do not call it in the explosion trigger. It preserves time, seed, transform and options and renders only offscreen. On a trigger, select an idle or finished instance, set its placement, call `reset()`, and drive `update(dt)` from the existing loop. Never steal an active instance. If all are busy, the host chooses to skip or allocate another; allocating still has a cost. Keep instances between bursts and call `dispose()` on scene teardown. Use `reset({variationSeed})` for deterministic replay. Rewarm after renderer/output changes or WebGL context restoration. Existing usage without prewarm remains supported. Environment ink is seeded from local luminance contrast before reduction, including dark silhouettes; narrow grid lines are filtered by gradient persistence. backgroundSuppression defaults to 0.35 and reduces environment shapes/trails in brush poses. Clean/release retain authored quiet-background treatment; terminal blasts use their own composition. Flat inputs cannot reveal invisible boundaries. Terminal detonate/shock retain the bright core and eight primary beams with burst-style broken ink edges and environment trails. Both share a fixed brush pattern anchored to the gameplay hit; shock expands it. strokeLength and inkDensity control the brush; zero length keeps the base blast. Ink trails, primary beams and both star polarities now narrow spatially at their tips. Star hearts, main-arm thickness, authored spans, hit anchors and 400 ms timing are retained; no extra spike ring or global sharpening is added. ## 持續治癒、護盾與雷流 / Continuous support VFX `verdant-restoration`, `aegis-weave`, `sustained-arc` retain 8 selected layer types and the original palette. These scene objects require their shared glow render hook: call `fx.render(renderer, scene, camera)` once after updating all effects, **instead of** `renderer.render(scene, camera)`. It draws all three kinds in that scene through one shared three-scale glow pipeline (full-resolution scenes; half/quarter/eighth-size halos). Plain `update()` plus `renderer.render()` omits the required glow. WebGL2, Three.js 0.187.0; no external images. Use the existing game loop. `size` and `speed` are the only appearance controls. 這三款保留8種圖層與原配色。主要畫面保持完整尺寸,光暈分別以半寬、四分之一與八分之一尺寸運算;外觀與光感維持。每幀更新所有特效後,使用其中一款的 `render` 代替原本的場景繪製,才能保留外散光;同場三款共用一次光暈流程。不另開動畫迴圈。`stop()` 淡出,`reset()` 原地重播,離開場景才 `dispose()`。不提供回血、阻擋或傷害邏輯。輔助展示物不包含在下載。 ```js import createVFX from './verdant-restoration.js'; const fx = createVFX({size: 1, speed: 1}); scene.add(fx.group); // Inside your existing game loop / 放進既有遊戲迴圈: fx.update(deltaSeconds); fx.render(renderer, scene, camera); // replaces renderer.render(scene, camera) ``` Lightning only: `setEndpoints(start, end)` takes finite `THREE.Vector3` values in `group.parent` coordinates. Coincident endpoints stop the effect; set valid endpoints and `reset()` to resume. Reapply endpoints after size changes. Endpoint decorations rotate with the beam and do not project onto terrain. For healing/shield, the origin is the ground center, +Y up. These effects use SRGBColorSpace / NoToneMapping while rendering and restore the caller's settings afterward; integration with an existing postprocessing chain requires separate validation. The shared glow still requires two scene passes and six blur passes; its wide buffers are smaller, but it remains a higher-load feature, not a mobile performance guarantee. setLayer indices are 0–7; inspect layers for their current names. ## Fire Ball 第7級 / Level 7 Fire Ball `fireball` keeps the flight and uses a two-second hit, thirty small debris pieces and surface heat when `hit(position, normal)` receives a contact normal. Air hits omit surface marks. Call one active fireball's `fx.render(renderer, scene, camera)` once per frame after updating all effects, instead of `renderer.render`; this includes all fireball glow in that scene. The hook performs two scene passes plus eight screen passes, not one draw call. 火球保留飛行,命中兩秒;30顆小碎石在各自出生後0.35–0.65秒淡出。牆面餘熱需要命中位置與法線;一般空中命中不畫裂紋。每幀使用一顆火球的 render 接法合成同場火球光暈。支援不透明 SRGB/NoToneMapping 與標準輸出目標設定;透明輸出、ACES、綁定目標後另外改 viewport/scissor,以及其他後製串接尚未驗證,不能直接宣稱相容。目標裝置效能未測。 ## Desktop performance ratings load.level retains low, medium and high; without valid measurements it is unassessed. Also inspect load.status: only measured with matching source and method identity is a verified measurement. stale means the version changed, inconclusive means results crossed tiers, and blocked means reference calibration failed. None means lightweight. load.legacyLevel is a historical estimate only. load.reportUrl provides conditions and limitations. The reference desktop runs one default instance at 1920×1080, DPR1. The score uses the larger CPU/GPU P95 across stages with a measurement-noise allowance: low ≤0.5ms, medium >0.5–2ms, high >2ms. These are library-specific thresholds. First use, 4/8 instances and mobile are separate; ratings do not guarantee FPS or game capacity. Current website grades use the preserved code-based estimates when load.status is estimated. These are not measured timings or guarantees. The codeEstimate field records their basis; measurementStatus retains the unfinished benchmark status. ## Starblade Cataclysm V19 V19 shares immutable flight data between matching default swords, seeds and shape settings within one module and Three.js namespace. Animation time, transforms and materials remain independent. The last owner releases shared data. Custom swords stay private. Create multiple effects from the same downloaded module rather than duplicating modules. This is not an eight-instance 60 FPS guarantee on arbitrary hardware. Stable ID `starblade-cataclysm`. Fixed download `/downloads/effects/starblade-cataclysm.js`; verify SHA-256 against the current catalog. The single module includes the demo sword and MIT MeshSurfaceSampler; dependency: Three.js 0.187.0. - Add `fx.group`; call `update(deltaSeconds)` in your game loop. Duration is 21 visual seconds (10.5 at speed 2). `reset()` replays; `dispose()` releases effect-owned resources. `stop()` / `hit()` remain single-shot compatibility no-ops. - `setObject(loadedSword,{height:3,rotation:[0,0,0]})` accepts a rigid Mesh/Group, multiple parts/materials, radians, tip +Y. Success restarts; failure preserves the current sword. `setObject(null)` restores the demo. - Hierarchy/materials are cloned. Source geometry/textures remain caller-owned and must remain alive. The host controls character-weapon visibility. Skins, morphs, instances and custom shader hooks are unsupported. - `setSweepPath(new THREE.Vector3(2,0,0),new THREE.Vector3(22,0,0))` sets the default 20-unit path. Explicit endpoints are respected exactly: both y=0, distance at least 0.1. Invalid/overflowing derived GPU coordinates are rejected atomically. Success restarts and moves the beam, terrain and six explosions together. Short paths scale bursts by `2*min(1,length/10)`. - Size scales the effect; speed scales its full timeline. Density changes decorative stars only; detail changes motion variation. Color tints golden energy while preserving dark-red structure. Default/min/max rendered allocations are **49,974 / 48,503 / 51,902**, including 5,184 reused soft passes, 12 spherical instances and 5,008 default trails. Individual seeded constant-speed flights arrive by 6.5 seconds. Outer orbital radii remain doubled, with different revolution counts; medium/large stars each carry a 0.06–0.12-second trail. Clover height remains 12. The porous pale pressure shell plays once at **7.65–8.30**, followed by a two-second interval. The red flash begins at **10.30**; the cross unfolds at **10.34–10.54**. Center-out flash erosion ends at **11.10**, and charging continues. The laser fires at **13.1**, first contacts the ground at **13.18**, contracts linearly at **13.23–13.45**, and stops at **17.7**. Six explosions occur at **17.72 / 17.88 / 18.04 / 18.20 / 18.36 / 18.52**. Clover/cross/core stay through **19.12** and retire by **20.12**. Last smoke ends by **20.67**; terrain finishes at **21**. The thumbnail samples **11.9** without changing its fixed framing. The receiving end uses **16 larger soft main sprites, 64 secondary wisps and 16 glow sprites**, for 96 contact sprites and 528 total endpoint instances. Their continuous yellow-orange center blends into orange-red and diffuse red glow. They have no opaque cutout shell, hidden sphere depth proxy or whole-cloud pulse. All accept depth tests; the soft sprites do not write depth. Existing spark lines are secondary. Local emissive beam blending preserves contact light while remaining anchored to the exact hit point. The same 64 main billows per explosion keep their seeded position, rolling outline and motion as hot areas cool into deep-plum smoke. Ease-out travel overlaps local cooling; no separate smoke column replaces a finished fireball. Basal fire and surface-derived wisps continue. Fire/smoke diagnostic groups isolate hot/cold areas of the same billows. Solid surviving fire/smoke contours discard erased depth; soft edges retain depth tests. The 16 diagnostic indices are unchanged. Six bursts retain their 14 distinct roles, white-hot spherical main flashes, broken spherical waves, individual connected fracture graphs, angular hot shards and shared charcoal-red rock colors. The roughly 40.9 MB path atlas is rebuilt atomically on sword replacement and released on disposal. Flat XZ terrain overlay only: no excavation/collision change, uneven-ground fit, damage, audio or extra loop. Higher load is a code estimate, not a 60 FPS guarantee. V19 visual preservation and performance are recorded separately; these integration instructions do not declare eight-instance stability passed.