feat: split native scale and translation channels
This commit is contained in:
@@ -406,7 +406,7 @@ Worker functions decoded + annotated in the Ghidra project (updated 2026-07-09):
|
||||
`gfx_object_init_default`@`0x472810`), the setters `gfx_set_vec18/24/16c`(`0x47e960/e910/e800`), the getters
|
||||
`gfx_get_vec18/24`(`0x47f360/f2e0`).
|
||||
|
||||
#### The `0x21c–0x243` sprite transform / ANIMATION cluster (2026-07-07, recon — implementation pending)
|
||||
#### The `0x21c–0x243` sprite transform / ANIMATION cluster (2026-07-10, partial implementation)
|
||||
|
||||
The scene-completeness tracker (`tools/scene_opcode_coverage.py`) flagged a dense band of GAP ops in
|
||||
`0x21c–0x243` (+ `0x2bd/0x2bf`) — the **largest remaining rendering unknown** in SC0000 (e.g. `0x220`×66,
|
||||
@@ -429,14 +429,14 @@ animation/tween** — and two members were already named in prior RE (`0x234 gfx
|
||||
| `0x224` | `0x417550` (417xxx) | `0x231` | `0x423be0` | `0x23e` | `0x42a4a0` |
|
||||
| `0x225` | `0x4236a0` | `0x232` | `0x423c30` | `0x23f` | `0x42a520` |
|
||||
| `0x226` | `0x42a230` | `0x233` | `0x423cf0` | `0x240` | `0x4245f0` |
|
||||
| `0x227` | `0x42a2e0` | `0x234` | **anim_start ✎** | `0x241` | `0x4247e0` |
|
||||
| `0x227` | `0x42a2e0` | `0x234` | **set_rotation_cycle ✎** | `0x241` | `0x4247e0` |
|
||||
| `0x228` | `0x42a3a0` | `0x235` | `0x423e40` | `0x242` | `0x4249d0` |
|
||||
| | | | | `0x243` | `0x4182d0` (417xxx) |
|
||||
|
||||
(`0x2bd`→`0x4251c0`, `0x2bf`→`0x425240`. The handful of `0x417xxx` handlers are trivial/marker-shaped — the
|
||||
default-handler neighbourhood — and are almost certainly no-ops or arg-poppers; triage before modelling.)
|
||||
|
||||
**Contract (corrected 2026-07-09, representative ops `0x21e`/`0x220`, both `argc 6`):** these are
|
||||
**Contract (completed 2026-07-10, representative ops `0x21e`/`0x220`, both `argc 6`):** these are
|
||||
independent matrix channels, not two encodings of one vec3 property.
|
||||
|
||||
- `0x21e` normalizes operands 4–6, then `gfx_object_set_scale_channel` (`0x47eaa0`) stores timing at
|
||||
@@ -445,13 +445,16 @@ independent matrix channels, not two encodings of one vec3 property.
|
||||
- `0x220` passes raw operands 4–6 to `gfx_object_set_translation_channel` (`0x47ecc0`), stores timing at
|
||||
`obj+0x44/+0x58`, and calls `0x48afb1`, which writes them into matrix entries 12–14 at
|
||||
`obj+0x1ac`: a **translation matrix**.
|
||||
- `gfx_object_apply_transform_channels` (`0x472f00`) interpolates and combines both matrices separately.
|
||||
- `gfx_object_apply_transform_channels` (`0x472f00`) supplies the timing contract. Both channels use
|
||||
shared start timestamp `obj+0x34` and global frame-time `ctx+0xb550`, but have independent delay/duration:
|
||||
scale `obj+0x3c/+0x50`, translation `obj+0x44/+0x58`. Each holds its current matrix through the
|
||||
delay, linearly interpolates current→target for its duration, then commits the target and clears its own timing.
|
||||
Neither third component is opacity.
|
||||
|
||||
**Port implication:** `GfxState` must ultimately retain separate scale and translation matrices/timing.
|
||||
The current single `AnimTarget` plus Godot `TZ/100 = opacity` approximation is native-inaccurate and is
|
||||
now tracked as transform-compositor debt. It was not the cause of the lingering circle: that was the skipped
|
||||
`0x215`/`0x1f7`/`0x1fa` teardown above.
|
||||
**Port result (2026-07-10):** `GfxState` now retains separate current/target scale and translation
|
||||
channels with the native shared-start/independent-timing model. Godot scales around `V18` (anchor),
|
||||
applies translation independently, and never derives opacity from transform Z. The shared `AnimTarget`
|
||||
and `TZ/100` alpha tween are gone.
|
||||
|
||||
##### `anim_start`/`set_anim_clock` decoded + opening confirmed (2026-07-07, animation-slice Task 1)
|
||||
|
||||
@@ -464,22 +467,21 @@ both annotated) and grepping the SC0000 opening settles the animation model and
|
||||
objects. Its own plate comment states the payoff: "our port can drive animation in the host's per-frame loop
|
||||
while the VM is parked at wait-for-input; no VM/host frame-lockstep." → **validates the wall-clock-tween
|
||||
architecture directly.** SC0000: `set-anim-clock(G[0x624bb])` @`0x123bd`, `set-anim-clock(0x190=400)` @`0x13858`.
|
||||
- **`0x234 anim_start` (argc 5, cmd-type 0xb):** `gfx_anim_start(op1=handle, op2, (float)op3, (float)op4,
|
||||
(float)op5)`. `op2` = *this object's* animation duration (`label_1235a` maxes the per-object durations into the
|
||||
global clock); `(op3,op4,op5)` = the target transform vec3 the object animates **toward**. SC0000 opening
|
||||
@`0x00daf`: `anim_start(G[0x62457], 0x2328, 0, 0, local2)` on the INIT2 CG handles, then `call label_1235a`.
|
||||
- **`0x234 anim_start` (legacy mnemonic; argc 5, cmd-type 0xb):** following worker
|
||||
`gfx_object_set_rotation_cycle` (`0x47f060`) into `gfx_object_anim_interpolate` (`0x473ed0`)
|
||||
corrects its ABI to `(handle)(period_ms)(axis_x)(axis_y)(axis_z)`. Period is `obj+0x228`, axis is
|
||||
`obj+0x244..0x24c`, and the frame-clock consumer applies
|
||||
`360*((now-start)%period)/period` degrees. This is a cyclic **rotation** channel, not a target vec3
|
||||
for scale/translation and not opacity.
|
||||
- **The opening path uses the whole subsystem, early.** `0x21e`/`0x220` transform-sets fire from `0x00f73`
|
||||
onward (`0x21e (G[0x6245b]) 0 0x12c l0 l1 0x64`, `0x220 (G[0x62457]) 0x96 0x3e8 l1 l3 0`), on the same INIT2 CG
|
||||
handles (`G[0x62457]`,`0x6245b`,`0x6245c`) — this is the opening, **not** battle/debug. So the slice's ops are
|
||||
real and verifiable on screen.
|
||||
|
||||
**Corrected host model (supersedes the "per-object clock" wording above):**
|
||||
- **Global clock** (from `0x238`): one `AnimClockDurationTicks` + a generation/reset marker the host watches to
|
||||
reset its wall-clock `elapsed` to 0. The host tweens all armed objects over this duration.
|
||||
- **Per-object correction:** `0x21e` is scale and `0x220` is translation; they occupy distinct matrices and
|
||||
timing fields. No component of either channel is opacity.
|
||||
- **Residual:** finish separating these channels in `GfxState`/Godot and identify the remaining color/alpha
|
||||
channel consumers. Do not reuse transform Z as alpha.
|
||||
**Corrected host model:** `0x21e` scale and `0x220` translation run directly from frame-time
|
||||
`ctx+0xb550` using their own delay/duration; they do not use op `0x238` as their duration.
|
||||
Op `0x234` is the independent rotation cycle above. Op `0x238` still configures the separate
|
||||
`ctx+0x51b78/+0x51b7c` animation service used by its own family.
|
||||
|
||||
##### The opening render path is RETAINED, not immediate-mode (2026-07-08, ground-truth correction)
|
||||
|
||||
@@ -637,11 +639,11 @@ missing-default bug). See `docs/phase-a-slice-plan.md` A2b-Geometry.
|
||||
|
||||
The `AE*` flash/glow effects (and sprite motion) are a **native time-animated retained render loop**, not
|
||||
per-frame bytecode. Reversed + annotated in Ghidra:
|
||||
- **Retained objects carry animation state:** an *active* flag (obj`+0` bit 4), a **progress** counter
|
||||
(obj`+0x214`, starts 0), a **duration** (obj`+0x228`), and a **target vector** (obj`+0x244/248/24c`).
|
||||
- **`gfx_anim_start`** (`0x47f060`, worker for op **`0x234`**) configures a per-object animation: sets the
|
||||
flag, resets progress, stores duration + target. Op **`0x1fd`** (`gfx_op_0x1fd_set_vec_scaled`) sets a
|
||||
scaled 3-vector.
|
||||
- **Retained objects carry cyclic rotation state:** flag value `4` at `obj+0`, start timestamp
|
||||
`obj+0x214`, period `obj+0x228`, and axis `obj+0x244/248/24c`.
|
||||
- **`gfx_object_set_rotation_cycle`** (`0x47f060`, worker for legacy op **`0x234`**)
|
||||
configures that channel. `gfx_object_anim_interpolate` consumes it from frame-time `ctx+0xb550`
|
||||
as a repeating 0..360-degree axis rotation. Op **`0x1fd`** is a separate scaled 3-vector setter.
|
||||
- **Op `0x238`** (`gfx_op_0x238_set_anim_clock`) sets a **global animation clock**, **non-blocking**:
|
||||
`ctx+0x51b78 = 0` (elapsed), `ctx+0x51b7c = duration` (the max per-object duration; SC0000 `label_1235a`
|
||||
maxes a table to compute it). It does **not** loop/wait.
|
||||
@@ -652,9 +654,8 @@ per-frame bytecode. Reversed + annotated in Ghidra:
|
||||
VM is parked at wait-for-input — no blocking present op, no VM/host frame-lockstep** (the answer to the
|
||||
"frame loop" question).
|
||||
|
||||
Consequence: reproducing the fades needs a **retained per-frame animated compositor** — the current
|
||||
immediate-mode permanent canvas can neither fade nor clear. Design: `docs/superpowers/specs/2026-07-07-
|
||||
animated-compositor-design.md`.
|
||||
Consequence: animation needs a retained per-frame compositor. That architecture is live; the 2026-07-10
|
||||
matrix slice adds native one-shot scale/translation, while cyclic rotation remains a later affine step.
|
||||
|
||||
### The full gfx render model — surfaces + objects + composite (2026-07-07)
|
||||
|
||||
@@ -672,8 +673,8 @@ and snapshotted textures at draw time; symptoms: alternating grey, glow over bac
|
||||
op `0x1fb` draw-texture) → `gfx_object_bind_draw` (`0x47e870`): sets the object's **source slot** (`obj+4`),
|
||||
**source rect** (`obj+8..0x14` = left,top,right,bottom), **position** (`obj+0x24/28/2c` = V24), and the
|
||||
**visible** flag (bit 0). The object references its surface **by slot index, live** (re-resolved each frame),
|
||||
NOT a snapshot. Objects also carry anchor V18 (`obj+0x18`), animation (flag bit 2 + progress `obj+0x214` /
|
||||
duration `obj+0x228` / target `obj+0x244..`), and color/alpha (`0x202/0x203`).
|
||||
NOT a snapshot. Objects also carry anchor V18 (`obj+0x18`), independent scale and translation
|
||||
matrices/timing, cyclic rotation state, and color/alpha (`0x202/0x203`).
|
||||
|
||||
**Render frame** — `gfx_render_frame` (`0x4820b0`), driven by op `0x20c` present (`gfx_op_0x20c_present_frame`
|
||||
`0x4174a0`, which also updates the frame timer `ctx+0x51b64/68`): iterate the object registry **in ascending
|
||||
@@ -685,9 +686,8 @@ buffers (present). **Slot 0 is NOT special** — a normal slot; several objects
|
||||
**⇒ Faithful port:** a `SurfaceStore` (`slot → {image, colorkey}`, from create/set-texture) + an `ObjectStore`
|
||||
(`handle → {slot, srcRect, position, anchor, scale, anim, alpha, visible}`, from draw-texture + the gfx ops) +
|
||||
a host per-frame compositor that draws visible objects **in ascending-handle order** from their live surface,
|
||||
interpolating animations by elapsed time. No VM/host lockstep (op `0x238` clock is non-blocking; animations
|
||||
play during the wait-for-input park). Open detail for implementation: the exact scale/transform math in
|
||||
`gfx_object_composite` (`FUN_00472f00`/`FUN_00473ed0`).
|
||||
interpolating animations by elapsed time. No VM/host lockstep: animations play during the wait-for-input park.
|
||||
Scale/translation timing and anchored composition are implemented; full affine rotation remains deferred.
|
||||
|
||||
### Blend & transparency — colorkey + `0x202`/`0x203` color/alpha (2026-07-08)
|
||||
|
||||
|
||||
@@ -179,14 +179,14 @@ Native handler gfx_op_0x20c_present_frame (dispatch ctx[0x26c93+0x20c]) -> gfx_r
|
||||
- **evidence:** Ghidra handler 0x42a1b0; FUN_0047f2e0(obj op1) + 3x→FUN_00425fb0(2/3/4). label_12649 site 0x00c86 handle=G[0x62457] → G[0x62498/9/a].
|
||||
|
||||
### 0x21e `set-anim-transform-norm` (set-anim-transform-norm, argc 6)
|
||||
- **summary:** (handle)(delay)(duration)(sx)(sy)(sz) — set the normalized SCALE-matrix channel. Handler 0x423350 normalizes sx/sy/sz; gfx_object_set_scale_channel@0x47eaa0 stores timing at obj+0x3c/+0x50 and constructs a diagonal 4x4 scale matrix at obj+0xac. Separate from op 0x220 translation; neither channel is opacity.
|
||||
- **summary:** (handle)(delay_ms)(duration_ms)(sx)(sy)(sz) — set the normalized SCALE-matrix channel (100=identity). Target obj+0xac is linearly sampled from current obj+0x6c by gfx_object_apply_transform_channels@0x472f00 on frame-time ctx+0xb550, after delay and for duration, then committed. Shares only start timestamp obj+0x34 with op 0x220; neither Z is opacity.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra 0x47eaa0 calls matrix builder 0x48af1d, which writes sx/sy/sz to diagonal entries 0/5/10 and identity entry 15. gfx_object_apply_transform_channels@0x472f00 consumes this independently of obj+0x1ac.
|
||||
- **evidence:** Ghidra 0x47eaa0 calls matrix builder 0x48af1d for target obj+0xac. Consumer 0x472f00 uses delay obj+0x3c, duration obj+0x50, current obj+0x6c, target obj+0xac, shared start obj+0x34, and frame-time ctx+0xb550.
|
||||
|
||||
### 0x220 `set-anim-transform-abs` (set-anim-transform-abs, argc 6)
|
||||
- **summary:** (handle)(delay)(duration)(tx)(ty)(tz) — set the absolute TRANSLATION-matrix channel. Handler 0x4234e0 passes raw floats to gfx_object_set_translation_channel@0x47ecc0, which stores timing at obj+0x44/+0x58 and constructs an identity 4x4 matrix with translation at obj+0x1ac. Separate from op 0x21e scale; neither channel is opacity.
|
||||
- **summary:** (handle)(delay_ms)(duration_ms)(tx)(ty)(tz) — set the absolute TRANSLATION-matrix channel. Target obj+0x1ac is linearly sampled from current obj+0x16c by gfx_object_apply_transform_channels@0x472f00 on frame-time ctx+0xb550, after delay and for duration, then committed. Independent of op 0x21e scale; neither Z is opacity.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra 0x47ecc0 calls matrix builder 0x48afb1, which writes identity diagonal and tx/ty/tz to entries 12/13/14. gfx_object_apply_transform_channels@0x472f00 combines this independently of obj+0xac.
|
||||
- **evidence:** Ghidra 0x47ecc0 calls matrix builder 0x48afb1 for target obj+0x1ac. Consumer 0x472f00 uses delay obj+0x44, duration obj+0x58, current obj+0x16c, target obj+0x1ac, shared start obj+0x34, and frame-time ctx+0xb550.
|
||||
|
||||
### 0x228 `u00421940` (u00421940, argc 5)
|
||||
- **summary:** 0x228 query-position (succ)(handle)(outX)(outY)(outZ): read the object's current computed position into vars (worker FUN_0047cdd0). C# VM: writes V24 + success flag. See docs/engine-re.md §SC0000 anim cluster.
|
||||
@@ -209,8 +209,9 @@ Native handler gfx_op_0x20c_present_frame (dispatch ctx[0x26c93+0x20c]) -> gfx_r
|
||||
- **grounding:** source=kelebek, confidence=low
|
||||
|
||||
### 0x234 `anim-start` (anim-start, argc 5)
|
||||
- **summary:** (handle)(duration)(x)(y)(z) — animate object toward target vec3 (x,y,z) over the GLOBAL clock; cmd-type 0xb, worker gfx_anim_start. op2=this object's duration (label_1235a maxes into the clock). SC0000 opening @0xdaf on INIT2 CG handles. Handler 0x423da0; Kelebek VA 0x422060 is drift.
|
||||
- **summary:** (handle)(period_ms)(axis_x)(axis_y)(axis_z) — configure a cyclic ROTATION channel. Worker gfx_object_set_rotation_cycle@0x47f060 stores period obj+0x228 and axis obj+0x244..0x24c; interpolator 0x473ed0 applies 0..360 degrees from frame-time ctx+0xb550. Separate from scale, translation, opacity, and op 0x238's clock.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra handler 0x423da0 -> worker 0x47f060. gfx_object_anim_interpolate@0x473ed0 consumes obj+0x228/0x244 on ctx+0xb550 and builds an axis-angle rotation matrix with angle 360*((now-start)%period)/period.
|
||||
|
||||
### 0x238 `set-anim-clock` (set-anim-clock, argc 1)
|
||||
- **summary:** (duration) — set the GLOBAL animation clock: native ctx+0x51b78=0 (elapsed), +0x51b7c=duration. cmd-type 3. NON-BLOCKING: only configures; the render loop advances it and interpolates all animating objects. SC0000 opening @0x123bd/@0x13858. Handler 0x4240e0; Kelebek VA 0x422390 is drift.
|
||||
|
||||
@@ -348,17 +348,12 @@ Spec `docs/superpowers/specs/2026-07-07-gfx-animation-subsystem-design.md`; plan
|
||||
completeness gauge flagged.
|
||||
|
||||
**Implemented (VM records, engine 50/50, full parity — sweep 284 exit/13 STEP-LIMIT unchanged):** the four
|
||||
opening-path anim ops — `0x21e set-anim-transform-norm` / `0x220 set-anim-transform-abs` (per-object transform
|
||||
channel: target vec3 + 2 params + enable), `0x234 anim-start` (animate toward a target vec3 over the clock),
|
||||
`0x238 set-anim-clock` (**GLOBAL, non-blocking** duration). `GfxState` gained the per-object anim channel + a
|
||||
global clock (passive — non-Godot hosts read none of it, so trace parity holds); `RenderObject.AnimState` carries
|
||||
it to the compositor. **RE correction:** the clock is global 1-operand (not per-object), and `anim-start` carries
|
||||
the target — the `set_anim_clock` handler's own comment confirms the wall-clock design ("drive animation in the
|
||||
host's per-frame loop while the VM is parked at wait-for-input").
|
||||
opening-path anim ops — `0x21e`, `0x220`, `0x234`, and `0x238` — were first retained in
|
||||
the port here. **Superseded by the 2026-07-10 matrix slice below:** the original shared-target interpretation was
|
||||
wrong; `0x21e` is scale, `0x220` is translation, and `0x234` is cyclic rotation.
|
||||
|
||||
**Compositor (Godot):** a per-handle wall-clock alpha tween over the global clock + an alpha-aware `BlitLayer`
|
||||
(3rd vec component = opacity), plus `--shot-settle <frames>` to capture mid-tween. **Non-regressing:** SC0000
|
||||
opening page 2 is pixel-identical to baseline at settle 3 and 300; Godot selftest OK.
|
||||
**Historical compositor:** this slice temporarily used transform Z as alpha. The 2026-07-10 follow-up removes
|
||||
that approximation and applies the native separate scale/translation channels instead.
|
||||
|
||||
**Tracker delta (`scene_opcode_coverage.py SC0000`):** GAP 68→**64** ops (836→**741** instrs), impl 49→**53**,
|
||||
correctly-handled 60→**65/129 (50.4%)**.
|
||||
@@ -371,8 +366,8 @@ engine-re.md "opening render path is RETAINED"). `draw-texture` binds a retained
|
||||
handle array `G[0x62455..]`) and a per-object working slot `G[0x62452]`, with `sleep` (`0x64`/`0x3e8`/`0x2ee`)
|
||||
between steps. Our VM executes the whole load/draw/`sleep` burst **instantly** (no timing, no per-frame present),
|
||||
so we only ever see the *final* retained state; the intermediate `AE*` frames never get a frame to display. This
|
||||
per-object alpha channel is the correct foundation for **retained-sprite** animation (character fades/scales via
|
||||
the handle system), but the opening explosion needs **frame-pacing** — modeling the scene-coroutine / `sleep 0xc8`
|
||||
retained-object channel was the correct architectural seam, but the opening explosion needs **frame-pacing** —
|
||||
modeling the scene-coroutine / `sleep 0xc8`
|
||||
timing (`0x7b`/`0x140`/`0xc8`, still GAP) so the burst is *not* collapsed. That is the clearly-scoped next chunk
|
||||
for the visible opening animation, and a genuinely different subsystem than the transform/alpha channel landed
|
||||
here. (**Correction:** an earlier draft of this note called it "immediate-mode slot-0 blits" — wrong; the engine
|
||||
@@ -695,3 +690,24 @@ handles, so SC0000 skipped its explicit `0x1f7(handle,10)` + `0x1fa(slot)` clean
|
||||
`0x1f7` erases retained objects, and `0x1fa` clears the surface. A booted SC0000 integration regression
|
||||
asserts no visible resource `0x37` remains; engine suite **86/86**. **Live clicked-path validation
|
||||
confirmed the fix on 2026-07-10:** the magic circle now disappears at the intended transition.
|
||||
|
||||
### A2b — native scale/translation matrix channels ✅ DONE (2026-07-10)
|
||||
|
||||
Replaced the legacy shared `AnimTarget` / transform-Z-as-opacity approximation with the native channel
|
||||
split proven in Ghidra. Op `0x21e` now owns normalized scale (100 = identity; current `obj+0x6c`,
|
||||
target `obj+0xac`, delay/duration `obj+0x3c/+0x50`). Op `0x220` owns absolute translation
|
||||
(current `obj+0x16c`, target `obj+0x1ac`, delay/duration `obj+0x44/+0x58`). Consumer
|
||||
`gfx_object_apply_transform_channels` (`0x472f00`) establishes a shared first-frame start
|
||||
timestamp, independent delayed linear interpolation, and target commit on completion.
|
||||
|
||||
`GfxState` samples both channels from `FrameClock`; Godot applies scale around V18 plus independent
|
||||
translation, including nearest-neighbor scaling/flips in the software blitter. Op `0x234` no longer
|
||||
overwrites either matrix: the completed consumer RE corrected it to a separate cyclic rotation period+axis
|
||||
channel (retained now; affine rendering deferred). No transform Z value contributes to opacity.
|
||||
|
||||
**Focused validation:** six matrix/rotation tests cover channel independence, VM dispatch, shared-start
|
||||
delay/duration sampling, target commit, and non-opacity Z values. Engine **86/86** and Godot build pass.
|
||||
Booted SC0000 with `--shot-sequence` + `--gfx-log`: 180/180 PNGs, 152 log lines, and zero
|
||||
unresolved/error/NaN/Infinity outcomes. The visible circle expands around its anchor through sampled scales
|
||||
`1.00 → 1.22 → 1.44 → 1.66` while remaining `op=1.00`, then the retained object is logged
|
||||
`GONE`. This directly validates that scale is rendered as geometry rather than opacity.
|
||||
|
||||
@@ -136,6 +136,7 @@ texture ops (no GPU context) — run windowed for real scenes. User args (after
|
||||
- `--shot-sequence <dir> [--frames N]` — dump one PNG per rendered frame (`frame_0000.png…`, default N=180 ≈ 3s @60fps) then quit, auto-advancing past input waits. Verifies **time-based (sleep-paced) effects** — e.g. the opening `AE*` burst — as distinct frames, which a single `--shot` cannot. CPU/IO-heavy by design (a PNG every frame); a dev diagnostic, not a normal run. e.g. `godot --path godot -- --boot --shot-sequence out/seq --frames 300`.
|
||||
- `--sleep-scale <f>` — multiply every `sleep` (op 0xc8) duration by `f` (default 1.0). The authentic opening burst is only ~2 s, too fast to eyeball live; `--sleep-scale 5` stretches it to ~10 s so the paced sequence (arcane `AE*` → character CGs → settled BG) is watchable. Debug-only; leave at 1.0 for real playback.
|
||||
- `--gfx-log <file>` — **compositor + op diagnostic** (the tool that root-caused the grey background). Logs, per rendered frame, only the objects whose draw outcome **CHANGED** (drawn↔skip↔gone, resId, resolved file, `slot`, `src`/`dst`, `op`acity, `tintStr`ength) — quiet until something actually changes, so the exact frame a layer drops out (and why) stands out. Also traces every `set-texture`/`create-texture` **slot assignment** (via `GodotAdvHost.TraceOps`). Works live or with `--shot-sequence`. Use it before theorising about layering/blend/geometry: it showed the grey BG = the slot-selecting globals resolving to 0 → every texture collapsing into slot 0 (see engine-re.md §"Grey-background root cause"). e.g. `godot --path godot -- --boot --gfx-log out/gfx.log` then click to the bad page.
|
||||
Matrix-channel outcomes also include sampled `scale`/`trans` values. Parent directories are created automatically.
|
||||
|
||||
## Asset resolution / graphics
|
||||
|
||||
|
||||
Reference in New Issue
Block a user