File size: 5,609 Bytes
463f868
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# VM Implementation Guide: Opcodes & Fast Logic

This guide documents the high-performance Virtual Machine (VM) updates implemented in `engine/game/fast_logic.py`, specifically targeting the "Critical Gap" opcodes identified for Logic Coverage Speedup.

## 1. New Opcodes Implemented

The following opcodes have been added to the Numba-compiled `fast_logic.py` engine:

| Opcode | ID | Name | Description |
| :--- | :--- | :--- | :--- |
| **SELECT_MODE** | 30 | `O_SELECT_MODE` | Implements branching/modal logic via Jump Tables. |

| **TRIGGER_REMOTE** | 47 | `O_TRIGGER_REMOTE` | Recursively executes an ability from another card (e.g., from Hand or Stage). |
| **SEARCH_DECK** | 22 | `O_SEARCH_DECK` | Moves a specific card (chosen by AI) from Deck to Hand. |

| **LOOK_AND_CHOOSE** | 41 | `O_LOOK_AND_CHOOSE` | Reveals top N cards, adds one to hand, discards rest. |

| **ORDER_DECK** | 28 | `O_ORDER_DECK` | Reorders (reverses/shuffles) the top N cards of the deck. |

| **REDUCE_COST** | 13 | `O_REDUCE_COST` | Adds a cost-reduction modifier to continuous effects. |

| **REDUCE_HEART_REQ** | 48 | `O_REDUCE_HEART_REQ` | Adds a heart-requirement reduction modifier. |
| **REPLACE_EFFECT** | 46 | `O_REPLACE_EFFECT` | Sets a replacement effect flag/modifier. |

| **SWAP_CARDS** | 21 | `O_SWAP_CARDS` | Discards N cards from hand (filtering) and Draws N cards. |
| **PLACE_UNDER** | 33 | `O_PLACE_UNDER` | Moves a card from Hand to a member's Stage Energy. |

| **MOVE_MEMBER** | 20 | `O_MOVE_MEMBER` | Swaps two members (and their states) on the stage. |
| **ACTIVATE_MEMBER** | 43 | `O_ACTIVATE_MEMBER` | Untaps a member. (Fixed mapping from ID 17). |



## 2. Architecture Updates



To support these complex operations within the constraints of Numba (JIT compilation), two major architectural patterns were introduced:



### A. Recursion via Pass-by-Reference (`TRIGGER_REMOTE`)



Numba's type inference engine struggles with recursive function calls when functions return tuples that change state. To solve this for `TRIGGER_REMOTE`:



1.  **Refactored Signature**: `resolve_bytecode` no longer returns `(cptr, state, bonus)`.

2.  **Mutable Arrays**: It now accepts `out_cptr` and `out_bonus` as **Numpy arrays of size 1**.

3.  **In-Place Updates**: The function modifies `out_cptr[0]` and `out_bonus[0]` directly.

4.  **Recursive Call**: When `O_TRIGGER_REMOTE` is encountered, the VM looks up the target's bytecode and calls `resolve_bytecode` recursively, passing the same state arrays.



```python

# Pseudo-code pattern

@njit

def resolve_bytecode(..., out_cptr, out_bonus):

    # ... logic ...

    if op == O_TRIGGER_REMOTE:

        # Save state

        out_cptr[0] = cptr

        # Recursive call

        resolve_bytecode(..., out_cptr, out_bonus)

        # Reload state

        cptr = out_cptr[0]

```



### B. Jump Tables for Branching (`SELECT_MODE`)



Numba functions are linear. To implement "Choose One" modal effects:



1.  **Compiler**: `Ability.compile` (in `engine/models/ability.py`) generates a header block:

    *   `[O_SELECT_MODE, NumOptions, 0, 0]`

    *   Followed by `NumOptions` instructions of `[O_JUMP, Offset, 0, 0]`.

2.  **VM Logic**:

    *   Reads choice index from `flat_ctx[CTX_CHOICE_INDEX]`.

    *   Calculates the target Jump Instruction index: `ip + 1 + choice`.

    *   Reads the offset from that Jump instruction and executes the jump.



## 3. Dynamic Targeting (`MEMBER_SELECT`)



Targeting logic has been decoupled from bytecode hardcoding:



*   **Logic**: `if s == 10: s = int(flat_ctx[CTX_TARGET_SLOT])`

*   **Usage**: Any opcode (e.g., `BUFF`, `TAP`) can set its target slot (`s`) to `10`. The VM will then use the value provided by the Agent (in the Context Vector) at runtime.



## 4. Compiler Usage



The `Ability` class in `engine/models/ability.py` has been updated to automatically compile these structures.



```python

# Example: Creating a Modal Ability

ability = Ability(

    raw_text="Choose one: Draw 1 or Charge 1",

    trigger=TriggerType.ON_PLAY,

    effects=[Effect(EffectType.SELECT_MODE, 1)],

    # Ensure modal_options is set on the Ability or the Effect

    modal_options=[

        [Effect(EffectType.DRAW, 1)],

        [Effect(EffectType.ENERGY_CHARGE, 1)]

    ]

)



# Compiling

bytecode = ability.compile()

# Result: [SELECT_MODE, 2, ... JUMP ... JUMP ... DRAW ... JUMP_END ... CHARGE ... JUMP_END ...]

```



## 5. Additional Opcode Fixes



### Salvage vs. Untap Correction

- Previously, `O_RECOV_M` (ID 17) was incorrectly implemented as "Untap Member". In the official opcode list, ID 17 is `RECOVER_MEMBER` (Salvage from Discard), and `ACTIVATE_MEMBER` (Untap) is ID 43.

- **Fix**: The Untap logic has been moved to `O_ACTIVATE_MEMBER` (43). `O_RECOV_M` (17) and `O_RECOV_L` (15) are now placeholders (pass) because "Salvage" requires access to the Discard pile, which is not currently available in the fast VM state vector.



## 6. Testing



New tests in `tests/test_vm_opcodes.py` verify these features:

*   `test_select_mode_branching`: Verifies jump logic.

*   `test_trigger_remote`: Verifies recursion depth and state preservation.

*   `test_search_deck`: Verifies deck scanning and removal.

*   `test_look_and_choose`: Verifies "Look N, Pick 1, Discard Rest" logic.

*   `test_swap_cards`: Verifies discard/draw cycle.

*   `test_place_under`: Verifies moving cards to stage energy.

*   `test_move_member`: Verifies slot swapping.



Run tests with:

```bash

cargo test --manifest-path engine_rust_src/Cargo.toml test_vm_opcodes -- --nocapture

```