troupe
The whole of "Standing Ovation"'s figure slice, on one entity. Put it on the scene's `Figures` anchor and it owns everything under it:
figures.troupe
The whole of "Standing Ovation"'s figure slice, on one entity. Put it on the
scene's Figures anchor and it owns everything under it:
entity.find("Figures").component.add(asset.resolve("figures.troupe", "component"))
It builds two things and drives both off the film clock (ovation.film):
The performer is an articulated hierarchy — pelvis, torso, neck, head, two arms, two legs, and two eyes that move independently of each other. He walks on from the wings at shot 5, holds still for shot 6's 18-degree macro while his two eyes blink on two different clocks and one of them tracks the lens, bows at shot 7 with his head staying up through it, and walks downstage during shot 8 until he is standing exactly where shot 12's camera stands — at which point he stops being a figure in the film and becomes the one watching it.
The audience is 1,412 of him, drawn as four hardware-instanced populations
(torso, head, left hand, right hand). Their world matrices and shader lanes are
written every frame by crowd_pose.computeShader from a static seed block, so
the values never touch the CPU: this component sets fourteen params and
dispatches, and 5,648 transforms come out the other side.
Four waves cross the house and the film is the order they arrive in —
occupancy back to front (t 41→52), attention front to back (53→62),
rising front to back (67→73) and applause from t 60, converging as the
unison param collapses the rate spread and the offset spread onto one clock
across shot 12. Every wave carries a per-member delay and a per-member rate off
the seed block, so it arrives as a gradient rather than a switch thrown on the
whole room at once.
:rebuild() tears the slice down and builds it again from source — every mesh,
the generated greasepaint, the performer and the audience's buffers. audience
turns the population off, which is what Acts I and II look like.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2figures.troupe — the whole of slice B, on one entity. It puts two things in the Odeon Marisol and drives both off the film clock: THE PERFORMER, an articulated hierarchy under `Figures/Performer` — pelvis, torso, neck, head, two arms, two legs, and two eyes that move independently of each other. He walks on at shot 5, holds still for the 18-degree macro of shot 6, bows at shot 7, and walks downstage during shot 8 until he is standing where the camera stands in shot 12, at which point he stops being a figure in the film and becomes the one watching it. THE AUDIENCE, 1,412 of him, drawn as four hardware-instanced populations — torso, head, left hand, right hand — whose world matrices and shader lanes are written every frame by `crowd_pose.computeShader` from a static seed block. The values never touch the CPU: this component sets fourteen params and dispatches, and 5,648 transforms come out the other side. Four waves cross the house, and the film is the order they arrive in: OCCUPANCY t 41 -> 52, BACK TO FRONT. The seats fill from the rear wall forward, each figure rising into its seat from below the back of it, so shot 7's near foreground grows a head and shot 8's followspot finds every row it crosses already occupied. ATTENTION t 53 -> 62, FRONT TO BACK. Heads turn to hold the lens as it tracks up the centre aisle, twenty-six rows each a beat behind the last. Off again by 64: they go back to watching the stage. RISING t 67 -> 73, FRONT TO BACK. The house comes to its feet. APPLAUSE from t 60, and the `unison` param collapses the rate spread and the offset spread onto one clock across shot 12. Every one of them carries a per-member delay and a per-member rate, because the horror of this film is that 1,412 identical men get BETTER at doing the same thing together — which only reads if they start apart.
head( ) → void
headHero( ) → void
torso( ) → void
torsoHero( ) → void
hand( ) → void
ruff( ) → void
nose( ) → void
hairL( ) → void
hairR( ) → void
shoe( ) → void
upperArm( ) → void
foreArm( ) → void
thigh( ) → void
shin( ) → void
eye( ) → void
pupil( ) → void
lid( ) → void
ear( ) → void
greasepaint( ) → void
suit( ) → void
glove( ) → void
hair( ) → void
noseRed( ) → void
shoe( ) → void
eyeWhite( ) → void
eyeDark( ) → void
dropCrowd( ) → void
Drop every instanced draw this slice owns. It sweeps by BUFFER NAME rather than only releasing the handles it is holding, because a hot reload replaces the module's locals while the registrations the previous copy made are still live in the renderer — and an orphaned registration can be neither enumerated back into a handle nor dropped, so the population would silently double on every reload.
quat(ax: number, ay: number, az: number, angle: number) → void
`Transform.quatFromAxisAngle` answers four numbers; a transform field takes one table, so every rotation in here goes through this.
| arg | type | description |
|---|---|---|
| ax | number | |
| ay | number | |
| az | number | |
| angle | number |
quatMul(a: ?, b: ?) → void
| arg | type | description |
|---|---|---|
| a | ? | |
| b | ? |
authorMeshes( ) → number
Author every mesh as a committed asset under `/source/figures/`. This runs at AUTHORING time, never from `awake()`. `asset.create` files into `/zero/source` from an execute and into `/zero/runtime/assets` from other callers, ignoring `folder` when it does — so a component that authored its own meshes on every load would mint a second `clown_head` under a different root, and `asset.resolve` would then refuse the ambiguous leaf and take the whole slice down with it (#10451). The meshes are content: they are made once, committed, and only RESOLVED at load.
authorPaint( ) → void
The greasepaint is generated at load and bound onto the material as three LIVE textures. Freezing it into `.texture` assets and referencing those from `mat.yaml` is the obvious way to do this and it does not work: the generated pixels are correct — sampled straight out of the buffer the generator filled, the face is pale greasepaint at 230/213/193, the lozenges are 9/9/9 and the mouth is 155/12/12 — and every head drawn through the asset comes out a flat tan 107/79/68 with no lozenges and no mouth at all, on an entity draw and on the instanced population alike. That value is the AVERAGE of the whole sheet, which is what a texture served at its coarsest level looks like; the live texture carries one level and is sampled at it. So the paint is regenerated on every load (1.5 s) rather than stored. The cost of that choice is that anything re-registering the material off disk — a shader edit, a material hot-reload — puts the declared placeholder colours back and blanks every face in the film until this runs again, which is why it is also exposed as `:authorMeshes()`.
rnd(i: number, k: number) → number
A deterministic 0..1 from an integer and a channel. The film has to produce the same 2,160 frames twice, so no member's jitter may come from `math.random`.
| arg | type | description |
|---|---|---|
| i | number | |
| k | number |
seedCrowd( ) → void
place(x: number, y: number, z: number, tier: number) → void
| arg | type | description |
|---|---|---|
| x | number | |
| y | number | |
| z | number | |
| tier | number |
writeSeeds(duck: boolean) → void
| arg | type | description |
|---|---|---|
| duck | boolean |
buildCrowd( ) → void
buf(name: string, elem: string, len: number) → void
Every one of these is readable back to the CPU on purpose. A wave that crosses 1,412 figures is a claim about a gradient of angles, and the only way to check a gradient is to read the matrices the GPU actually wrote — a screenshot shows that SOMETHING turned, not that the twelfth row is behind the eleventh.
| arg | type | description |
|---|---|---|
| name | string | |
| elem | string | |
| len | number |
drawPart(mesh: ?, material: ?, bufName: string) → void
| arg | type | description |
|---|---|---|
| mesh | ? | |
| material | ? | |
| bufName | string |
crowdIsDrawing( ) → boolean
Whether the renderer is actually DRAWING this slice's four populations. Neither of the two obvious checks answers this. `renderer.mesh.isResident` returns true for a mesh uploaded on the current frame, which `drawInstanced` then refuses as "not resident in the renderer's GPU mesh store" — two different stores, one predicate. And `instanceInfo` returns a record for a draw the renderer refused, so a registration that never produced a draw call reads exactly like one that did. What does not lie is the material identity table: a population that is being drawn has a row in it, and one that is not has none.
stepCrowdBuild( ) → void
restartCrowdBuild( ) → void
part(parent: ?, name: string, mesh: ?, material: ?, pos: ?) → void
Every part of the performer is marked TEMPORARY. He is rebuilt from this file on every load, so his twenty-odd entities belong to the live session and not to the scene: left ordinary, each one is a pending scene edit, and the whole set of them refuses every other slice's `world.commit` until somebody bakes a performer into `scene.json` that the next load would only spawn again.
| arg | type | description |
|---|---|---|
| parent | ? | |
| name | string | |
| mesh | ? | |
| material | ? | |
| pos | ? |
buildPerformer( ) → void
setPerformerVisible(on: boolean) → void
| arg | type | description |
|---|---|---|
| on | boolean |
poseArm(a: ?, swing: number, elbow: number, out: number) → void
| arg | type | description |
|---|---|---|
| a | ? | |
| swing | number | |
| elbow | number | |
| out | number |
poseLeg(l: ?, swing: number, knee: number) → void
| arg | type | description |
|---|---|---|
| l | ? | |
| swing | number | |
| knee | number |
restPose( ) → void
animatePerformer(t: number) → void
| arg | type | description |
|---|---|---|
| t | number |
poseCrowd(t: number) → void
| arg | type | description |
|---|---|---|
| t | number |
tick(t: number, _dt: number, _shot: any) → void
| arg | type | description |
|---|---|---|
| t | number | |
| _dt | number | |
| _shot | any |
buildFrame( ) → void
Standing the population up is the one thing in this slice that does NOT run on the film clock. It takes several frames whatever the clock is doing, and a film parked on one frame for authoring dispatches its subscribers once — so a build driven off the clock would stall halfway and leave the house empty until somebody happened to scrub the timeline.
reset( ) → void
waves( ) → void
Where the film's four waves have got to on the frame the clock last dispatched — the occupancy, attention and rising fronts in metres of house depth, how much of the house is attending, how hard they are clapping, and how tightly the applause has converged.
examples
entity.find("Figures").component.get("figures.troupe"):waves().unisonreadPose(which: string, first: number, count: number) → boolean
Start a GPU readback of one of the population's live pose buffers, so a wave can be checked as NUMBERS rather than looked at. `which` is "body", "head", "handL", "handR" or "lanes"; the read lands a frame or two later, so call :sample() to drain it.
| arg | type | description |
|---|---|---|
| which | string | Which buffer to read: body, head, handL, handR or lanes. |
| first | number | Index of the first member to read, 0-based. |
| count | number | How many members to read. |
examples
local c = entity.find("Figures").component.get("figures.troupe"); c:readPose("head", 0, 46)sample( ) → void
Drain the readback :readPose started, as a flat array of floats. Nil until the GPU has answered, which is a frame or two after the call.
seedOf(i: number) → void
The seat foot point, facing and per-member delays this member was seeded with — the static half of what poses them, for checking a wave against the room.
| arg | type | description |
|---|---|---|
| i | number | Member index, 0-based; 0..1195 are the stalls, 1196..1411 the balcony. |
authorMeshes( ) → number
Re-author every one of the slice's meshes under /source/figures from the geometry code, then rebuild. Call it from an `execute` after changing `figures.clown` or `figures.geom` — `asset.create` files an asset somewhere else entirely when it is not an execute that calls it (#10451).
examples
entity.find("Figures").component.get("figures.troupe"):authorMeshes()rebuild( ) → void
Tear the slice's contribution down and build it again from the committed meshes — the greasepaint, the performer's hierarchy and the audience's buffers.
awake( ) → void
update(_dt: number) → void
| arg | type | description |
|---|---|---|
| _dt | number |
editorUpdate(_dt: number) → void
| arg | type | description |
|---|---|---|
| _dt | number |
onDestroy( ) → void
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Showing up to 3 levels deep. Open a part to see what it holds further down.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.