Conjurations grow into the world, wrapped in glowing swirls
Nothing appears fully-formed anymore. Summoned creatures and conjured terrain volumes scale up from a sliver, bottom-anchored, as if growing out of the floor. Conjured SOLIDS — walls, firewalls, stone, and yes, illusion walls for whoever they fool — RISE: while growing, the rays pass over them so the room behind stays visible, and the renderer draws the young face climbing bottom-up with violet swirls running across it, solidifying as it tops out. Every creation also blooms the shimmer at its cell. The growth is driven through the fx channel, so the director's slate applies: things grow only once the camera faces them. Two camera notes squared away en route: the slate-setter read back the state it had just written, registering itself as its own dependency (an update-depth ping-pong under fast stepping) — locals now; and a reel whose FINAL step strides into a corner gets one more turn after the walk lands, to a view worth ending on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
cd9ce9fddf
commit
285c91b2c8
@@ -313,28 +313,31 @@
|
||||
aimed = true;
|
||||
}
|
||||
}
|
||||
// How good is the view down heading `a` from (px,py)? Corridor depth,
|
||||
// and at the reel's end, a bonus for visible subjects.
|
||||
const viewScoreAt = (px: number, py: number, a: number): number => {
|
||||
let score = Math.min(castRay(v, px, py, a).dist, 8);
|
||||
if (!atEnd) return score;
|
||||
const bonus = (cell: { x: number; y: number }, worth: number) => {
|
||||
const bx = cell.x + 0.5, by = cell.y + 0.5;
|
||||
const bearing = Math.atan2(by - py, bx - px);
|
||||
if (Math.abs(shortestArc(a, bearing)) > 0.55) return 0;
|
||||
const d = Math.hypot(bx - px, by - py);
|
||||
if (d < 0.1) return 0;
|
||||
const sight = castRay(v, px, py, bearing);
|
||||
return !sight.warped && sight.dist >= d - 0.4 ? worth / (1 + d * 0.3) : 0;
|
||||
};
|
||||
for (const p of v.players) if (p.alive && p.id !== povId) score += bonus(p.position, 6);
|
||||
for (const c of v.creatures) score += bonus(c.position, 5);
|
||||
for (const t of v.treasures) if (t.position && !t.carriedBy) score += bonus(t.position, 3);
|
||||
return score;
|
||||
};
|
||||
if (!aimed) {
|
||||
// Nothing aims the eyes. If they'd idle nose-to-brick (the opening
|
||||
// cut, a dead-end beat — or the reel's CLOSING shot, which gets a
|
||||
// higher standard), find a better view: the deepest corridor, and
|
||||
// on the final frame, preferably one with something in it.
|
||||
const viewScore = (a: number): number => {
|
||||
let score = Math.min(castRay(v, tx, ty, a).dist, 8);
|
||||
if (!atEnd) return score;
|
||||
const bonus = (cell: { x: number; y: number }, worth: number) => {
|
||||
const bx = cell.x + 0.5, by = cell.y + 0.5;
|
||||
const bearing = Math.atan2(by - ty, bx - tx);
|
||||
if (Math.abs(shortestArc(a, bearing)) > 0.55) return 0;
|
||||
const d = Math.hypot(bx - tx, by - ty);
|
||||
if (d < 0.1) return 0;
|
||||
const sight = castRay(v, tx, ty, bearing);
|
||||
return !sight.warped && sight.dist >= d - 0.4 ? worth / (1 + d * 0.3) : 0;
|
||||
};
|
||||
for (const p of v.players) if (p.alive && p.id !== povId) score += bonus(p.position, 6);
|
||||
for (const c of v.creatures) score += bonus(c.position, 5);
|
||||
for (const t of v.treasures) if (t.position && !t.carriedBy) score += bonus(t.position, 3);
|
||||
return score;
|
||||
};
|
||||
const viewScore = (a: number): number => viewScoreAt(tx, ty, a);
|
||||
// A cut lands with whatever stale facing it carried, so a cut with
|
||||
// nothing aimed always re-picks its view from the new position.
|
||||
const staring = willCut ||
|
||||
@@ -351,15 +354,32 @@
|
||||
targetFacing = bestA;
|
||||
}
|
||||
}
|
||||
// The reel's LAST step may be a stride into a corner: the walk keeps
|
||||
// its own facing, then the camera turns once more to a view worth
|
||||
// ending on.
|
||||
let closingArc = 0;
|
||||
if (atEnd && aimed && castRay(v, tx, ty, targetFacing).dist < 1.2) {
|
||||
const current = viewScoreAt(tx, ty, targetFacing);
|
||||
let bestA = targetFacing, bestScore = current;
|
||||
for (const a of [0, Math.PI / 2, Math.PI, -Math.PI / 2]) {
|
||||
const sc = viewScoreAt(tx, ty, a);
|
||||
if (sc > bestScore + 0.5) { bestScore = sc; bestA = a; }
|
||||
}
|
||||
closingArc = shortestArc(targetFacing, bestA);
|
||||
}
|
||||
const stepIdx = Math.min(idx, steps.length - 1);
|
||||
const prevView = stepIdx > 0 ? steps[stepIdx - 1]!.view : null;
|
||||
/** Hold the OLD world on screen this long, so the outcome lands only
|
||||
* once the eyes face its recipient: a cutaway gets a beat of the
|
||||
* "before"; a turn holds until the turn is done. */
|
||||
const setSlate = (holdMs: number) => {
|
||||
heldView = holdMs > 60 && prevView ? prevView : null;
|
||||
camPlan = { idx: stepIdx, holdMs: heldView ? holdMs : 0 };
|
||||
if (heldView) {
|
||||
// Locals only: reading heldView back here would register it as a
|
||||
// dependency of this very effect, and the timer clearing it would
|
||||
// re-trigger us into a ping-pong.
|
||||
const hold = holdMs > 60 && prevView ? prevView : null;
|
||||
heldView = hold;
|
||||
camPlan = { idx: stepIdx, holdMs: hold ? holdMs : 0 };
|
||||
if (hold) {
|
||||
const t = setTimeout(() => (heldView = null), holdMs);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
@@ -379,6 +399,7 @@
|
||||
const turnMs = (hurled ? 0 : Math.min(260, Math.abs(arc) * 180)) / speed;
|
||||
const walkMs = (dist > 0.05 ? (hurled ? 260 : 420) : 0) / speed;
|
||||
const clearSlate = setSlate(aimed && aim ? turnMs + 120 / speed : 0);
|
||||
const closingMs = Math.min(300, Math.abs(closingArc) * 200) / speed;
|
||||
const t0 = performance.now();
|
||||
let raf = 0;
|
||||
const tick = (now: number) => {
|
||||
@@ -394,8 +415,12 @@
|
||||
const ease = w * w * (3 - 2 * w);
|
||||
cam.x = fromX + (tx - fromX) * ease;
|
||||
cam.y = fromY + (ty - fromY) * ease;
|
||||
} else if (closingArc !== 0 && t < turnMs + walkMs + closingMs) {
|
||||
cam.x = tx; cam.y = ty;
|
||||
const w = (t - turnMs - walkMs) / closingMs;
|
||||
cam.facing = fromF + arc + closingArc * w * w * (3 - 2 * w);
|
||||
} else {
|
||||
cam.facing = fromF + arc;
|
||||
cam.facing = fromF + arc + closingArc;
|
||||
cam.x = tx; cam.y = ty;
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user