Juice and polish pass the presentation phase after the playtest proof
world_playtest_scenario proved the player can act, the score changes, and the win phase fires (see playtest-verification-loop). The game is playable and plain: flat lighting, primitive-gray meshes, a functional but unframed camera, silence, no impact on contact. Polish is the next phase, and it is a distinct phase on purpose. Run it AFTER the proof so you are decorating aUse this for
Not for
Pairs with: Game loop assembly entities scripts HUD win shippable loop, Playtest verification loop proving gameplay executed evidence, Atmosphere sky lighting fog and time of day, Materials and looks PBR surfaces color metalness and textures, Camera feel field of view smoothing tone mapping and post, Game feel juice hit flash camera kick and impact, Audio sound effects music and positional feedback, Particles explosions and visual effects, Asset to instances model prefab instance authoring chain
When to use
The loop already WORKS. world_playtest_scenario proved the player can act, the score changes, and the win phase fires (see playtest-verification-loop). The game is playable and plain: flat lighting, primitive-gray meshes, a functional but unframed camera, silence, no impact on contact. Polish is the next phase, and it is a distinct phase on purpose. Run it AFTER the proof so you are decorating a real loop, not painting a scene that might still change shape.
Everything below is a real op verified in the tree. Where a juice idea has no authorable op yet (screen shake, hitstop, squash), the honest gap is named in game-feel-and-juice and in the roadmap, not faked here.
The polish pass, in order
Run these as one pass. Earlier steps set the frame the later steps read (a material palette reads better under a real lighting rig; a hero frame composes the lit, textured scene).
LIGHTING AND SKY. Land a lighting rig and an atmosphere as world-build ops in the same document transaction (
world_build_apply_semantic_operations):lighting.rig.applyfor a sun-plus-fill rig, and the world-build operation environment.sky_atmosphere.apply (or environment.atmosphere.apply) for the sky and fog mood. Deep control of individual lights and local fog is onLightComponentandLocalEnvironmentVolumeComponent(see atmosphere-sky-and-lighting). A lit scene is the single biggest step up from plain.MATERIAL PALETTE. Give the world a coherent look with
material.palette.apply(world-build), then refine specific hero surfaces with the twelve ai_safe PBR ops throughapply_material_semantic_patch(project.material.patch.apply): base color, metallic, roughness, emissive, alpha, texture slots (see materials-and-looks). Set a material once and reuse it across meshes so the palette stays coherent.CAMERA HERO FRAME AND POST LOOK. Compose the shot with
camera.hero_frame.set(orcamera.frame_targetto point at the objective). Tune the feel onCameraComponentdirectly: fov, follow smoothing,toneMapping,exposure, and thepostProcessingblock (depthOfField,motionBlur) (see camera-feel). Apply a global renderer grade withrenderer.visual_profile.apply(MCP toolworld_apply_visual_profile). Post settings are clamped by one policy authority against live backend capabilities, so a profile that the running backend cannot honor degrades safely rather than crashing.WORLD-TEXT LABELS. Anchor readable labels to entities with a
TextRenderableComponent: enemy nameplates, floating values, item labels, and quest markers, rendered through the MSDF atlas so they stay crisp at any distance. This is the diegetic-UI layer that a HUD panel (layer 4 of assembly) does not cover. A player's own name over their character is not this layer: it comes from the character policy nameplate setting, which stamps a server-writtenNameplateComponent(see player-characters-and-spawns), so do not hand-author a player name plate.AUDIO. Audio is LIVE in production Play. The audio subsystem is mounted above the present-path branch (main-thread, serving both the Worker and main-thread present paths), so
AudioEmitterComponentbeds andctx.audioone-shots are audible in a real session, not just a test fixture. Add looping ambience and background music withctx.audio.setBackgroundMusic/ anAudioEmitterComponent, and event stingers (a pickup chime, a hit thud) withctx.audio.play/playSpatial, pitched and leveled per event (see audio-feedback). Need a bespoke stinger the catalog lacks? Generate one withgeneration_create_job(theaudio_generatorkind), import it as a project asset, and reference it from the emitter (credit-metered, see step 7).IMPACT CUES (the juice moment). On a contact or score moment, fire the transient presentation cues through the script
fxhost:particleBurstfor a one-shot spark or poof anddecalfor a scorch or paint mark (theFx.particleBurstandFx.decalscript nodes). These ride the wired presentation cue lane (audience-scoped, replayed byte-identically from the cue id, never leaked to a non-recipient), and they are cosmetic by construction so they never mutate the authoritative sim. Layer them with aHighlightComponentflash pulse and a smallctx.camera.addLookInputkick to assemble a punchy hit (see game-feel-and-juice). Persistent effects (a torch's fire, a continuous stream) stay onParticleEmitterComponent(see particles-and-visual-effects); the cue is for the one-shot.HERO MODELS (replace the placeholders). The loop was proven with primitive placeholders (a gray box is a chest, a capsule is an enemy). For the HERO objects the player looks at, replace the placeholder with a generated 3D model:
generation_create_jobwith a model capability (model_generator; providers include hunyuan3d21, trellis, sf3d) andtarget.importOnSuccess: truefinalizes a variant-bearing project asset, which you then reference from aRenderableComponentshape mesh assetId and instance through the asset-to-instances corridor. COST NOTE: generation is an async, credit-metered job (quote it first withgeneration_quote_jobwhen budget matters). Spend it on the few hero objects, not every prop; keep primitives or catalog reuse for background fill (see asset-acquisition-strategy).DISCLOSURE NORM: primitive placeholders and marker stand-ins (the
asset.placeholder.requestbillboard, a cone-for-a-tree, a gray box for a chest) are legitimate mid-build state, but they are NOT finished fidelity. If any HERO object is still a placeholder when you hand back a scenery or look-and-feel brief, SAY SO in your summary by name: which objects remain primitives or markers, and that they await a generated or catalog mesh. The failure this prevents is reporting a scene as "polished" or "done" while the player would see cones and boxes. An honest summary names the remaining placeholders; it never lets structure being present read as fidelity being achieved.
Why this is a separate phase, and its order
- Polish decorates a proven loop. If you light and texture before the loop is proven, a gameplay change that moves or deletes entities orphans the work. The playtest is the gate; polish comes after it.
- Lighting before materials: a palette is judged under the rig that will ship, not under flat default light.
- The camera hero frame after lighting and materials: you are framing the lit, textured scene, not a gray one.
- Generation (step 7) is last and selective because it is the one step that costs credits and runs asynchronously; everything above is synchronous authored state.
What is NOT available (do not fake it)
- No native
screenShake,hitstop,squash, or transform-tween op. Approximate a shake with an oscillatingctx.camera.addLookInputkick and a hitstop with a state-gated pause; these are approximations, not one-call effects (see game-feel-and-juice). Naming these as done is the failure mode this playbook exists to prevent. - No transient full-screen post pulse (a damage vignette flash) as a cue;
renderer.visual_profile.applyand theCameraComponentpost fields are durable global writes, not one-shot pulses.
Verify
- Reload the world and confirm it MOUNTS and renders the lit, textured, framed scene (a backend 200 is not proof; load the SPA).
- Trigger a contact moment in a playtest and confirm the particle burst fires, the sound plays, and the highlight flashes.
project_get_graph_snapshot: the palette, camera, lighting, and any TextRenderableComponent labels and generated hero assets are present on the entities you expect.- LOOK before you claim fidelity: a snapshot proves structure, not appearance. Frame the hero objects with
world_observe_entity_view(see scenery-composition) to confirm they render as real meshes, not placeholders. If any hero is still a primitive or marker, or the render came back unavailable, disclose that in the summary rather than calling the scene done.