feat(packages): ludic.net - a party over UDP: peers, reliable ordered delivery, the hello's frame and the host's pids, join codes, room codes through a matchmaker with STUN, punching and a relay, the MTU cap and the wire codec; messages in on an inbox, the session's end and a silent guest as facts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 07:45:26 +03:00
parent 411206e45a
commit 12172d7b75
20 changed files with 1762 additions and 0 deletions

View file

@ -0,0 +1,83 @@
# room.ludic - a party across two home routers. The host registers its outside and inside
# addresses with the matchmaker and is given a six-letter room code; a guest hands over the code
# and its own addresses and gets the host's back. Behind one router, the inside addresses.
const NET_R_IDLE: int = 0
const NET_R_STUN: int = 1 # asking what this machine looks like from outside
const NET_R_REGISTER: int = 2 # the host: asking for a room
const NET_R_ROOM: int = 3 # the host: registered, watching for guests
const NET_R_ASK: int = 4 # a guest: asking the room for the host
const NET_R_DONE: int = 5 # a guest: the hello is on its way
var net__r_state: int = 0
var net__r_role: int = 0 # 1 host, 2 guest
var net__r_t: float = 0.0
var net__r_tries: int = 0
var net__pub_ip: int = 0
var net__pub_port: int = 0
var net__room: string = "" # the host's room code, or the one a guest asked for
var net__http: int = 0
var net__poll_t: float = 0.0
var net__relay_t: float = 0.0
var net__dial_heard: float = 0.0
# room codes are on when the game named a matchmaker
export function net_room_on() -> bool { return len(net__conf().signal_url) > 0 }
export function net_room() -> string { return net__room }
export function net_room_asking() -> bool { return net__r_state == NET_R_STUN or net__r_state == NET_R_ASK }
# the host (after net_host or net_become_host): a room at the matchmaker, if there is one
export function net_room_host() -> void {
if not net_room_on() { return }
net__r_role = 1
net__room = ""
net__seen = new []string
net__stun_begin()
}
# a guest by room code: the matchmaker's answer brings the host's address (the hello is set first)
export function net_room_join(code: string) -> bool {
if not net_room_on() {
net__why = NET_WHY_NO_MATCHMAKER
return false
}
net__init()
if not net__socket() { return false }
net__r_role = 2
net__room = Text.upper(code)
net__wait_more = 20 # the matchmaker answers before the host does
net__wait_for_host()
net__stun_begin()
return true
}
function net__stun_begin() -> void {
net__r_state = NET_R_STUN
net__r_t = 0.0
net__r_tries = 0
net__pub_ip = 0
net__pub_port = 0
if net__conf().no_stun {
net__r_tries = 3
return
}
net__stun_send()
}
function net__room_body(role_key: string) -> string {
var pub = ""
if net__pub_ip != 0 { pub = net__addr_text(net__pub_ip, net__pub_port) }
return "{\"" + role_key + "\": \"" + pub + "\", \"local\": \"" + net__local_text() + "\"}"
}
function net__json_str(v: Val, key: string) -> string {
if v == null or value_has(v, key) == 0 { return "" }
return value_as_str(value_get(v, key))
}
function net__addr_text(ip: int, port: int) -> string { return `{Udp.ip_text(ip)}:{port}` }
function net__local_text() -> string {
var ip = NetSocket.local_ip()
if ip == 0 { ip = Udp.ip("127.0.0.1") }
return net__addr_text(ip, net_port())
}