Skip to content

V Matrix

Provides access to the V Matrix system — read your owned and equipped V Cores, and send craft / equip / remove / enhance requests to the server.

Action semantics — read before scripting any send_* function

1. The return value is dispatch-only. send_* returns true once the packet is on the wire — not when the server applies it. The server can silently reject (wrong state, equipped core, rate limit, lock check, etc.). Do not treat true as success.

2. inventory_index is positional and is NOT stable across server mutations. It's the index into the owned-cores array. When the server accepts a remove / disassemble / craft, the array reshuffles and every higher inventory_index decrements by one. A snapshot from a previous tick will then point at a different core, or off the end. Re-read get_slots() after every accepted action.

3. Verify by observed state change, not by the return value. Watch a server-published signal:

  • send_disassemble_single_request / send_craft_requestget_shard_count() change
  • send_equip_request / send_remove_requestget_equipped() change for that board slot
  • send_enhance_slot_requestget_matrix_point() change and the target board slot's enhance change

4. Use item_sn as the stable identity. If you're tracking a specific core across ticks (per-core cooldowns, "already tried this one" sets), key on item_sn (int64 serial). The SN survives every inventory mutation; the inventory_index does not.

5. One mutation per tick. The real client UI gates these actions behind confirmation dialogs, so the server does not expect bursts. Send one request, wait until you observe the state change (or time out), then send the next.

Canonical send-and-verify loop

For anything that mutates the inventory in bulk (mass-disassemble, mass-craft), drive it from on_tick one request at a time and gate the next send on the observed effect:

lua
local pending_sn = nil       -- item_sn we last asked to disassemble
local pending_shards = 0     -- get_shard_count() at time of request
local sent_at_ms = 0
local attempted = {}         -- item_sn -> tick we last tried (skip on retry)
local TIMEOUT_MS = 3000

function on_tick()
    local shards_now = core.vmatrix.get_shard_count()

    if pending_sn then
        if shards_now > pending_shards then
            -- Server accepted: shards went up, slot list has reshuffled.
            pending_sn = nil
        elseif core.get_update_time() - sent_at_ms > TIMEOUT_MS then
            -- 3s with no shard change — give up on this one and move on.
            pending_sn = nil
        else
            return  -- still waiting for confirmation
        end
    end

    for _, s in ipairs(core.vmatrix.get_slots()) do
        if not s.protect_lock and not attempted[s.item_sn] and is_junk(s) then
            attempted[s.item_sn] = core.get_update_time()
            pending_sn = s.item_sn
            pending_shards = shards_now
            sent_at_ms = core.get_update_time()
            core.vmatrix.send_disassemble_single_request(s.inventory_index)
            return
        end
    end
end

Slot Object Structure

get_slots() and get_slot_by_index() return objects with these fields. A slot is one owned V Core (in your inventory of cores, equipped or not).

PropertyTypeDescription
inventory_indexnumberPosition in the owned-cores array — this is the data_index value passed to send_*_request. Not stable across server mutations: when a remove / disassemble / craft is accepted, every higher inventory_index decrements by one. Re-read get_slots() after each accepted action; for cross-tick bookkeeping, key on item_sn instead
item_snnumberUnique int64 serial number of the core. Stable across all inventory mutations — use this when you need to identify the same core on a later tick
idnumberV Core template ID
gradenumberCore grade
expnumberCurrent experience
slotnumberInventory slot
skill1numberSkill ID embedded in slot 1
skill2numberSkill ID embedded in slot 2
skill3numberSkill ID embedded in slot 3
slot_indexnumberBoard position when equipped, or -1 if the core is not on the V Matrix board. Not the right key for send_*_request — use inventory_index
expire_datenumberFILETIME (100ns ticks since 1601-01-01). A value pointing at 2079-01-01 is the "never expires" sentinel
protect_lockbooleanWhether the core is protect-locked from disassembly

Equipped Slot Object Structure

get_equipped() returns objects representing equipped V Matrix board slots. Each entry references a slot from get_slots() via data_index.

PropertyTypeDescription
data_indexnumberinventory_index of the equipped core (-1 if empty)
slot_indexnumberPosition on the V Matrix board
enhancenumberSlot enhancement level
extensionbooleanWhether the board slot has been extended

Read Functions

core.vmatrix.get_slots() -> table<slot>

Returns an array of all owned V Core slots.

lua
local slots = core.vmatrix.get_slots()
for _, s in ipairs(slots) do
    core.log(string.format("Core %d (slot_index=%d, grade=%d)", s.id, s.slot_index, s.grade))
end

core.vmatrix.get_equipped() -> table<equip_slot>

Returns an array of equipped V Matrix board slots.

lua
local equipped = core.vmatrix.get_equipped()
for _, e in ipairs(equipped) do
    core.log(string.format("Board slot %d: data_index=%d enhance=%d",
        e.slot_index, e.data_index, e.enhance))
end

core.vmatrix.get_slot_count() -> number

Returns the number of owned V Core slots.


core.vmatrix.get_equip_count() -> number

Returns the number of equipped V Matrix board slots.


core.vmatrix.get_shard_count() -> number

Returns the player's current V Core Shard count — the currency consumed by V Core crafting / enhancement. Reads the count field of the player's V Core Shard quest record. Returns 0 if the character isn't loaded.

lua
local shards = core.vmatrix.get_shard_count()
core.log("V Core Shards: " .. shards)

core.vmatrix.get_matrix_point() -> number

Returns the player's current Matrix Point balance — the currency consumed by slot enhancement / extension. Returns 0 if the character isn't loaded.

lua
local mp = core.vmatrix.get_matrix_point()
core.log("Matrix Points: " .. mp)

core.vmatrix.get_slot_by_index(slot_index: number) -> slot | nil

Looks up an owned slot by its board slot_index (i.e. its position on the V Matrix board). Returns nil if no equipped slot has that board position. To look up a core by its inventory index, just index get_slots() directly: get_slots()[inventory_index + 1].


Action Functions

core.vmatrix.send_craft_request(v_core_id: number, craft_count: number) -> boolean

Crafts craft_count V Cores of template v_core_id. Returns true if the packet was sent.

lua
core.vmatrix.send_craft_request(100001, 1)

core.vmatrix.send_equip_request(data_index1: number, data_index2: number, equip_index1: number, equip_index2: number, drag: boolean) -> boolean

Equips, swaps, or moves cores between board slots. The four indices match the LONG nDataIndex1, LONG nDataIndex2, LONG nEquipIndex1, LONG nEquipIndex2 packet shape; drag indicates the action originated from a drag-and-drop UI.

ParameterDescription
data_index1Source core's inventory_index (the one being placed)
data_index2inventory_index of the core currently at the destination, or -1 if the slot is empty
equip_index1Source board position, or -1 if the source is unequipped
equip_index2Destination board position
dragtrue if performed by drag-drop, false for click-equip

core.vmatrix.send_remove_request(data_index: number) -> boolean

Unequips the core with the given inventory_index from its board slot.

lua
for _, e in ipairs(core.vmatrix.get_equipped()) do
    if e.data_index ~= -1 then
        core.vmatrix.send_remove_request(e.data_index)
    end
end

core.vmatrix.send_enhance_request(data_index: number, src_data_index: number) -> boolean

Enhances the core at data_index (an inventory_index) by consuming the core at src_data_index as fuel.

lua
core.vmatrix.send_enhance_request(target.inventory_index, fuel.inventory_index)

core.vmatrix.send_disassemble_single_request(data_index: number) -> boolean

Disassembles a single unequipped core back into V Core Shards. The server rejects this against currently-equipped cores — call send_remove_request first if the core is on a board slot.

Do not call this in a tight loop

Each successful disassemble shifts every higher inventory_index down by one, so a burst of requests built from one get_slots() snapshot will target the wrong cores after the first one is accepted. Send one request per tick and gate the next on get_shard_count() going up — see the canonical send-and-verify loop above.

lua
-- Disassemble every unequipped, non-locked core into V Core Shards.
-- One request per tick; advances only after the previous one is confirmed by
-- the shard count incrementing.

local pending_sn, pending_shards, sent_at = nil, 0, 0

function on_tick()
    local shards_now = core.vmatrix.get_shard_count()
    if pending_sn then
        if shards_now > pending_shards then
            pending_sn = nil
        elseif core.get_update_time() - sent_at > 3000 then
            pending_sn = nil  -- timeout — server probably rejected
        else
            return
        end
    end

    local equipped = {}
    for _, e in ipairs(core.vmatrix.get_equipped()) do
        if e.data_index ~= -1 then equipped[e.data_index] = true end
    end
    for _, s in ipairs(core.vmatrix.get_slots()) do
        if not s.protect_lock and not equipped[s.inventory_index] then
            pending_sn, pending_shards = s.item_sn, shards_now
            sent_at = core.get_update_time()
            core.vmatrix.send_disassemble_single_request(s.inventory_index)
            return
        end
    end
end

core.vmatrix.send_disassemble_multiple_request(data_indices: table) -> boolean

Disassembles several unequipped cores in one packet. Takes an array of inventory_index values. Returns false if the table is empty.

Unlike send_disassemble_single_request, this is the bulk-disassemble path used by the in-game "select multiple → disassemble" UI — the whole batch is processed server-side as one transaction, so you don't need the one-per-tick get_shard_count() gating loop for it.

Snapshot once, send once

Build the indices from a single get_slots() snapshot. Don't call this in a loop — the server will reshuffle every inventory_index once the batch lands, and any subsequent batch built from the same snapshot will target the wrong cores. Re-read get_slots() after the shard count goes up if you need to send another batch.

lua
-- Mass-disassemble every unequipped, non-locked core in one go
local equipped = {}
for _, e in ipairs(core.vmatrix.get_equipped()) do
    if e.data_index ~= -1 then equipped[e.data_index] = true end
end

local indices = {}
for _, s in ipairs(core.vmatrix.get_slots()) do
    if not s.protect_lock and not equipped[s.inventory_index] then
        table.insert(indices, s.inventory_index)
    end
end

if #indices > 0 then
    core.vmatrix.send_disassemble_multiple_request(indices)
end

core.vmatrix.send_enhance_slot_request(slot_index: number) -> boolean

Raises the enhance level of the board slot at slot_index, spending Matrix Points. Returns false without doing anything if the player has no Matrix Points, so you don't have to check first.

lua
for _, e in ipairs(core.vmatrix.get_equipped()) do
    if e.enhance < 25 and core.vmatrix.get_matrix_point() > 0 then
        core.vmatrix.send_enhance_slot_request(e.slot_index)
    end
end

core.vmatrix.use_nodestone(amount: number) -> boolean

Uses amount Nodestones from the player's inventory.

lua
core.vmatrix.use_nodestone(1)

Examples

List every owned V Core with its skills

lua
for _, s in ipairs(core.vmatrix.get_slots()) do
    core.log(string.format("[inv=%d] id=%d grade=%d skills=%d/%d/%d %s",
        s.inventory_index, s.id, s.grade, s.skill1, s.skill2, s.skill3,
        s.protect_lock and "(LOCKED)" or ""))
end

Map equipped board slots to core IDs

lua
local slots = core.vmatrix.get_slots()
for _, e in ipairs(core.vmatrix.get_equipped()) do
    if e.data_index == -1 then
        core.log(string.format("Board %d: empty", e.slot_index))
    else
        local s = slots[e.data_index + 1]  -- inventory_index is 0-based
        core.log(string.format("Board %d: core %d (enh=%d)",
            e.slot_index, s and s.id or -1, e.enhance))
    end
end