docs(gfx): reverse the full gfx command-buffer op contract (Phase 1)

Ghidra-read all 13 still-stubbed gfx-family handlers via the dispatch table
(0x1a2/0x1f7/0x1fa/0x1ff/0x202/0x203/0x212/0x213/0x216/0x217/0x218/0x219/0x21a);
recovered 0x219 (was unanalyzed). Every op writes a cmd-type into the current
object record then SETs fields (native worker) or QUERYs fields (returns to
operands). Recorded the op-contract table in engine-re.md, set opcodes.toml
semantics (source=investigation, confidence=high), renamed handlers in Ghidra.

Key finding: the drift has TWO stubbed drivers, not one — 0x215 (slot-select)
AND 0x218/0x21a (per-object geometry vectors feeding the anchor-preserve math).
The subsystem is a per-object record model (slot + position/anchor 3-vectors +
color) queried by the ops; native DirectDraw workers need not be modelled. This
is the spec for Phase 2 (host-side model design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-07-07 17:01:14 -04:00
parent e6f6803a15
commit e0ac60e668
4 changed files with 197 additions and 139 deletions

View File

@@ -3457,16 +3457,16 @@ abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "gfx-cmd-register"
category = "draw"
summary = "graphics command-buffer op: sets current gfx-object cmd-type=3 and registers a '%c%8.8x' key from operand 1"
summary = "0x1a2 (val) — gfx cmd-type 3. Handler gfx_op_0x1a2_registry_insert @0x42d360: builds key '%c%8.8x'(3, operand-desc) and INSERTS operand 1 into the gfx command-buffer registry (FUN_0042cf70, open-addressing hash). This is what POPULATES the registry that op 0x215 queries. NOT save/scene (raw Kelebek VA 0x428010 drifted to op 0x1ac save handler). See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "investigation"
confidence = "med"
confidence = "high"
depends_on = []
evidence = "Ghidra: real handler FUN_0042d360 (via dispatch table ctx[0x26c93+op]); sets *(ctx+0x53d88+ctx[0x53d14]*0x78)=3, sprintf(\"%c%8.8x\",3,op1), FUN_0042cf70. NOT save/scene (raw Kelebek VA 0x428010 drifted to op 0x1ac save handler). See docs/engine-re.md"
evidence = "Ghidra: real handler FUN_0042d360 (via dispatch table ctx[0x26c93+op]); sets *(ctx+0x53d88+ctx[0x53d14]*0x78)=3, sprintf(\"%c%8.8x\",3,op1), FUN_0042cf70 (hash insert; counterpart of op 0x215 find). NOT save/scene (raw Kelebek VA 0x428010 drifted to op 0x1ac save handler). See docs/engine-re.md"
[[opcode.semantics.args]]
i = 1
role = ""
role = "value"
observed_types = ["g-int", "l-ptr"]
[[opcode]]
@@ -4505,14 +4505,14 @@ argc = 2
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "ui-elem?"
name = "gfx-elem-create"
category = "draw"
summary = "2 args; 0x420 family, pairs with 0x1fa — create/begin a UI element"
summary = "0x1f7 (handle)(count) — gfx cmd-type 5. Handler gfx_op_0x1f7_elem_create @0x422270: if count>1 → FUN_0047d8b0(handle,count) (array element), else FUN_0047d850(handle) (single). In label_12649 it selects the object after a 0x215 slot-query, before 0x1fa clears the slot. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "med"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x422270 (dispatch ctx[0x26c93+0x1f7]); FUN_0047d8b0/FUN_0047d850 on operand1 gated by operand2>1."
[[opcode.semantics.args]]
i = 1
@@ -4598,14 +4598,14 @@ argc = 1
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "ui-clear?"
name = "gfx-elem-release"
category = "draw"
summary = "1 arg (element id); follows 0x1f7 — show/hide/clear UI element by id"
summary = "0x1fa (idx) — gfx cmd-type 3. Handler gfx_op_0x1fa_elem_release @0x4224a0: releases the element at [ctx+0x52bd4 + idx*4] (virtual free, then nulls the slot) + FUN_00474e40(idx). In label_12649 it clears the working slot G[0x62452] after a 0x215/0x1f7 pair. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "med"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x4224a0 (dispatch ctx[0x26c93+0x1fa]); frees ctx+0x52bd4[operand1*4] via vtbl, then FUN_00474e40(operand1)."
[[opcode.semantics.args]]
i = 1
@@ -4752,14 +4752,14 @@ argc = 4
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "draw?"
name = "set-gfx-geom3-c"
category = "draw"
summary = "4 args (global+imms); follows 0x217, then call"
summary = "0x1ff (handle)(a)(b)(c) — gfx cmd-type 9. Handler gfx_op_0x1ff_set_geom3 @0x4227b0: SETS a 3-vector (int→float a,b,c) on object `handle` via native worker FUN_0047e800 (sibling of 0x217/0x219, a distinct per-object vector). See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x4227b0 (dispatch ctx[0x26c93+0x1ff]); FUN_0047e800(op1,(float)op2,(float)op3,(float)op4)."
[[opcode.semantics.args]]
i = 1
@@ -4788,14 +4788,14 @@ argc = 5
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "draw-blit?"
name = "gfx-blit-color"
category = "draw"
summary = "5 args (coords/sizes); preceded by coord arithmetic, near draw ops"
summary = "0x202 (handle)(x)(y)(alpha)(color) — gfx cmd-type 0xb. Handler gfx_op_0x202_blit_color @0x4228d0: blits object `handle` at (x,y) with a packed ARGB built from alpha(op4, ≥0x100→0xff, <0→FUN_0047f3e0) and color(op5, <0→FUN_0047f3e0) → FUN_0047ea00. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "med"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x4228d0 (dispatch ctx[0x26c93+0x202]); packs (alpha<<24|rgb) from operands 4/5, FUN_0047ea00(op1,op2,op3,packed)."
[[opcode.semantics.args]]
i = 1
@@ -4829,14 +4829,14 @@ argc = 4
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "draw?"
name = "gfx-draw-color"
category = "draw"
summary = "4 args; chains with 0x202/draw-texture"
summary = "0x203 (handle)(v)(alpha)(color) — gfx cmd-type 9. Handler gfx_op_0x203_draw_color @0x4229a0: draws object `handle` with op2 + a packed ARGB from alpha(op3)/color(op4) → FUN_0047e9b0. Sibling of 0x202 with one fewer positional arg. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "med"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x4229a0 (dispatch ctx[0x26c93+0x203]); packs color from operands 3/4, FUN_0047e9b0(op1,op2,packed)."
[[opcode.semantics.args]]
i = 1
@@ -5190,23 +5190,23 @@ argc = 2
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u00421090"
category = "unknown"
summary = ""
name = "set-gfx-field64"
category = "draw"
summary = "0x212 (obj_idx)(val) — gfx cmd-type 5. Handler gfx_op_0x212_set_field64 @0x4230c0: obj=[ctx+0x14d54 + obj_idx*4]; if obj: *(obj+0x64)=val. Sets one per-object field. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra handler 0x4230c0 (dispatch ctx[0x26c93+0x212]); writes [obj+0x64]=operand2, obj from ctx+0x14d54[operand1*4]."
[[opcode.semantics.args]]
i = 1
role = ""
role = "obj_idx"
observed_types = ["imm"]
[[opcode.semantics.args]]
i = 2
role = ""
role = "value"
observed_types = ["imm"]
[[opcode]]
@@ -5216,28 +5216,28 @@ argc = 3
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u004210D0"
category = "unknown"
summary = ""
name = "set-gfx-xy"
category = "draw"
summary = "0x213 (obj_idx)(x)(y) — gfx cmd-type 7. Handler gfx_op_0x213_set_field68_6c @0x423110: obj=[ctx+0x14d54 + obj_idx*4]; if obj: *(obj+0x68)=x; *(obj+0x6c)=y (an (x,y) pair). See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra handler 0x423110; writes obj+0x68/+0x6c from operands 2/3, obj from ctx+0x14d54[operand1*4]."
[[opcode.semantics.args]]
i = 1
role = ""
role = "obj_idx"
observed_types = ["imm"]
[[opcode.semantics.args]]
i = 2
role = ""
role = "x"
observed_types = ["imm", "l-int"]
[[opcode.semantics.args]]
i = 3
role = ""
role = "y"
observed_types = ["imm"]
[[opcode]]
@@ -5273,23 +5273,23 @@ argc = 2
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u004211A0"
category = "unknown"
summary = ""
name = "query-gfx-field?"
category = "draw"
summary = "0x216 (out)(idx) — gfx cmd-type 5. Handler gfx_op_0x216_query_table46d14 @0x42a0f0: out = *(ctx+0x46d14 + idx*0x14). A per-object field query over a stride-0x14 table. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra handler 0x42a0f0; reads ctx+0x46d14[operand2 * 0x14], writes operand1 via FUN_00425fb0(1,·)."
[[opcode.semantics.args]]
i = 1
role = ""
role = "out"
observed_types = ["g-int", "l-int"]
[[opcode.semantics.args]]
i = 2
role = ""
role = "idx"
observed_types = ["imm", "g-int", "l-int"]
[[opcode]]
@@ -5299,33 +5299,33 @@ argc = 4
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "gfx-geom?"
name = "set-gfx-geom3"
category = "draw"
summary = "4 global-ints; part of a 0x217/0x218/0x21a geometry chain"
summary = "0x217 (handle)(a)(b)(c) — gfx cmd-type 9. Handler gfx_op_0x217_set_geom3 @0x4231b0: SETS a 3-vector (int→float a,b,c) on object `handle` via native worker FUN_0047e960. In SC0000 label_12649 it writes the anchor vector G[0x6249b/c/d] INTO the object; op 0x218 reads it back. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x4231b0 (dispatch ctx[0x26c93+0x217]); FUN_0047e960(op1,(float)op2,(float)op3,(float)op4). label_12649 sites e.g. 0x00c67 handle=G[0x62457], vec=G[0x6249b/c/d]."
[[opcode.semantics.args]]
i = 1
role = ""
role = "handle"
observed_types = ["imm", "g-int", "l-int", "l-ptr"]
[[opcode.semantics.args]]
i = 2
role = ""
role = "a"
observed_types = ["imm", "g-int", "l-int", "l-ptr"]
[[opcode.semantics.args]]
i = 3
role = ""
role = "b"
observed_types = ["imm", "g-int", "l-int"]
[[opcode.semantics.args]]
i = 4
role = ""
role = "c"
observed_types = ["imm", "g-int"]
[[opcode]]
@@ -5335,14 +5335,14 @@ argc = 4
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "gfx-geom?"
name = "get-gfx-geom3?"
category = "draw"
summary = "4 global-ints; chains with 0x21a/0x217"
summary = "0x218 (handle)(out_a)(out_b)(out_c) — gfx cmd-type 9. Handler gfx_op_0x218_query_geom3 @0x42a130: GETS a stored 3-vector from object `handle` (FUN_0047f360) into out_a/b/c. In label_12649 it reads the object's anchor vector back into G[0x6249b/c/d] — a stubbed DRIVER of the render drift (stale anchor → bad centering). See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
evidence = "Ghidra handler 0x42a130; FUN_0047f360(obj op1) + 3x FUN_00550850→FUN_00425fb0(2/3/4). label_12649 site 0x00c8f handle=G[0x62457] → G[0x6249b/c/d]."
[[opcode.semantics.args]]
i = 1
@@ -5371,14 +5371,14 @@ argc = 4
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "u004212E0"
category = "unknown"
summary = ""
name = "set-gfx-geom3-b"
category = "draw"
summary = "0x219 (handle)(a)(b)(c) — gfx cmd-type 9. Handler gfx_op_0x219_set_geom3 @0x423240: SETS a 3-vector (int→float) on object `handle` via native worker FUN_0047e910 (sibling of 0x217, a different per-object vector). See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "kelebek"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = ""
evidence = "Ghidra handler 0x423240 (was unanalyzed; function created this session; dispatch ctx[0x26c93+0x219]); FUN_0047e910(op1,(float)op2,(float)op3,(float)op4)."
[[opcode.semantics.args]]
i = 1
@@ -5407,14 +5407,14 @@ argc = 4
abi_source = "kelebek+decode-validated"
[opcode.semantics]
name = "gfx-geom?"
name = "get-gfx-geom3-b?"
category = "draw"
summary = "4 global-ints; chains with 0x218/0x217"
summary = "0x21a (handle)(out_a)(out_b)(out_c) — gfx cmd-type 9. Handler gfx_op_0x21a_query_geom3 @0x42a1b0: GETS a stored 3-vector from object `handle` (FUN_0047f2e0) into out_a/b/c. In label_12649 it reads the object's position vector into G[0x62498/9/a] — a stubbed DRIVER of the render drift. See docs/engine-re.md gfx op-contract table."
noop_headless = false
source = "inference"
confidence = "low"
source = "investigation"
confidence = "high"
depends_on = []
evidence = "confirm via frida"
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]."
[[opcode.semantics.args]]
i = 1