TLDR:

I got the bridge working and the duel technically running, but my confident intern (Claude) led me down a rabbit hole of trying to parse the byte-based messages from scratch. There was real progress for a bit, but after reaching a broken state where messages were being parsed incorrectly and data sizes were misaligned, I took a step back and decided to change how the communication would work.

ygopro-core directly references YGOpen for message protocol definitions. Instead of continuing to reverse-engineer byte layouts manually, I’ll be implementing that encoder/decoder layer properly and using protobuf definitions as the contract between systems.


Implementing the Callbacks

Before you can create a duel, the engine requires three function pointers wired into OCG_DuelOptions:

These are implemented as C functions inside the CGo preamble (the comment block above import "C"), which is the standard pattern for CGo callbacks. For now they’re stubs:

That’s enough to satisfy the engine's requirements and get a duel handle.

/*
#cgo LDFLAGS: -L${SRCDIR} -locgcore
#include "ocgapi.h"
#include <stdlib.h>
#include <string.h>

// cardReader: called by engine to get card stats by code.
// For now we return a blank card so the engine doesn't crash.
void cardReaderStub(void* payload, uint32_t code, OCG_CardData* data) {
memset(data, 0, sizeof(OCG_CardData));
data->code = code;
}

// cardReaderDone: called after cardReader so we can free memory.
// Nothing to free in our stub.
void cardReaderDoneStub(void* payload, OCG_CardData* data) {}

// scriptReader: called by engine to load a Lua script by name.
// Returning 0 means "script not found" — engine will skip it.
int scriptReaderStub(void* payload, OCG_Duel duel, const char* name) {
return 0;
}

// logHandler: called by engine to emit log messages.
void logHandlerStub(void* payload, const char* str, int type) {
// We'll wire this to Go's logger later
}
*/

With these wired in, OCG_CreateDuel returned a valid duel handle — a pointer to a live game state object in C++ memory.


Setting Up a Test Hand and Starting the Duel

To exercise the engine, we added 5 copies of Dark Magician (card code 46986414) to player 0's hand, plus 40-card decks for both players, then called OCG_StartDuel.

// Add 5 copies of Dark Magician (code 46986414) to player 0's hand
fori:=0;i<5;i++ {
bridge.AddCard(duel,46986414,0,bridge.LOC_HAND,uint32(i),bridge.POS_FACEUP)
}

// Add 40 copies to player 0's deck so the engine doesn't immediately error
fori:=0;i<40;i++ {
bridge.AddCard(duel,46986414,0,bridge.LOC_DECK,uint32(i),bridge.POS_FACEDOWN)
}

// Same for player 1
fori:=0;i<40;i++ {
bridge.AddCard(duel,46986414,1,bridge.LOC_DECK,uint32(i),bridge.POS_FACEDOWN)
}

bridge.StartDuel(duel)
fmt.Println("Duel started, processing...")