# WebCC Schema Definition File
# Format (pipe-separated):
# NAMESPACE|TYPE|NAME|FUNC_NAME|TYPES(space-separated)|JS_ACTION
# Lines starting with '#' are comments.
#
# Handle types: Use handle(TypeName) for typed handles, e.g.:
#   RET:handle(DOMElement) - returns a DOMElement handle
#   handle(CanvasContext2D):ctx - takes a CanvasContext2D handle named ctx
#
# This generates type-safe handle wrappers in C++ that are distinct at compile time.
#
# Inheritance: Use meta|inherit|Derived|Base to allow implicit conversion:
meta|inherit|Canvas|DOMElement
meta|inherit|Image|DOMElement
meta|inherit|Audio|DOMElement

# ------------------------------------------------------------------------------
# DOM
# ------------------------------------------------------------------------------
dom|command|GET_BODY|get_body|RET:handle(DOMElement)|{ if(!elements[0]) elements[0] = document.body; return 0; }
dom|command|GET_ELEMENT_BY_ID|get_element_by_id|string:id RET:handle(DOMElement)|{ const el = document.getElementById(id); if(!el) { console.warn('get_element_by_id: element not found', id); return -1; } const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); elements[handle] = el; return handle; }
dom|command|CREATE_ELEMENT|create_element|string:tag RET:handle(DOMElement)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const el = document.createElement(tag); elements[handle] = el; return handle; }
dom|command|CREATE_ELEMENT_SCOPED|create_element_scoped|string:tag string:scope RET:handle(DOMElement)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const el = document.createElement(tag); el.setAttribute('coi-scope', scope); elements[handle] = el; return handle; }
dom|command|CREATE_ELEMENT_DEFERRED|create_element_deferred|int32:handle string:tag|{ const el = document.createElement(tag); elements[handle] = el; }
dom|command|CREATE_ELEMENT_DEFERRED_SCOPED|create_element_deferred_scoped|int32:handle string:tag string:scope|{ const el = document.createElement(tag); el.setAttribute('coi-scope', scope); elements[handle] = el; }
dom|command|CREATE_COMMENT|create_comment|string:text RET:handle(DOMElement)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const el = document.createComment(text); elements[handle] = el; return handle; }
dom|command|CREATE_COMMENT_DEFERRED|create_comment_deferred|int32:handle string:text|{ const el = document.createComment(text); elements[handle] = el; }
dom|command|CREATE_TEXT_NODE|create_text_node|string:text RET:handle(DOMElement)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const el = document.createTextNode(text); elements[handle] = el; return handle; }
dom|command|CREATE_TEXT_NODE_DEFERRED|create_text_node_deferred|int32:handle string:text|{ const el = document.createTextNode(text); elements[handle] = el; }
dom|command|SET_NODE_VALUE|set_node_value|handle(DOMElement):handle string:text|{ const el = elements[handle]; if(el) el.nodeValue = text; }
dom|command|SET_ATTRIBUTE|set_attribute|handle(DOMElement):handle string:name string:value|{ const el = elements[handle]; if(!el){ console.warn('set_attribute: unknown element handle', handle); continue; } el.setAttribute(name, value); }
dom|command|SET_PROPERTY|set_property|handle(DOMElement):handle string:name string:value|{ const el = elements[handle]; if(!el){ console.warn('set_property: unknown element handle', handle); continue; } el[name] = value; }
dom|command|GET_ATTRIBUTE|get_attribute|handle(DOMElement):handle string:name RET:string|{ const el = elements[handle]; if(!el){ console.warn('get_attribute: unknown element handle', handle); return 0; } const ret = el.getAttribute(name) || ""; }
dom|command|APPEND_CHILD|append_child|handle(DOMElement):parent_handle handle(DOMElement):child_handle|{ const parent = elements[parent_handle]; const child = elements[child_handle]; if(!parent || !child){ console.warn('append_child: unknown handles', parent_handle, child_handle); continue; } parent.appendChild(child); }
dom|command|INSERT_BEFORE|insert_before|handle(DOMElement):parent_handle handle(DOMElement):child_handle handle(DOMElement):ref_handle|{ const parent = elements[parent_handle]; const child = elements[child_handle]; const ref = elements[ref_handle]; if(!parent || !child){ console.warn('insert_before: unknown handles', parent_handle, child_handle); continue; } parent.insertBefore(child, ref || null); }
dom|command|REMOVE_ELEMENT|remove_element|handle(DOMElement):handle|{ const el = elements[handle]; if(!el){ console.warn('remove_element: unknown element handle', handle); continue; } el.remove(); elements[handle] = undefined; }
dom|command|MOVE_BEFORE|move_before|handle(DOMElement):parent_handle handle(DOMElement):node_handle handle(DOMElement):ref_handle|{ const parent = elements[parent_handle]; const node = elements[node_handle]; const ref = elements[ref_handle]; if(!parent || !node){ console.warn('move_before: unknown handles', parent_handle, node_handle); continue; } parent.insertBefore(node, ref || null); }
dom|command|SET_INNER_HTML|set_inner_html|handle(DOMElement):handle string:html|{ const el = elements[handle]; if(el) el.innerHTML = html; }
dom|command|SET_INNER_TEXT|set_inner_text|handle(DOMElement):handle string:text|{ const el = elements[handle]; if(el) el.innerText = text; }
dom|command|ADD_CLASS|add_class|handle(DOMElement):handle string:cls|{ const el = elements[handle]; if(el) el.classList.add(cls); }
dom|command|REMOVE_CLASS|remove_class|handle(DOMElement):handle string:cls|{ const el = elements[handle]; if(el) el.classList.remove(cls); }
dom|event|CLICK|handle(DOMElement):handle
dom|command|ADD_CLICK_LISTENER|add_click_listener|handle(DOMElement):handle|{ const el = elements[handle]; if(el) el.dataset.c = handle; }
dom|event|INPUT|handle(DOMElement):handle string:value
dom|command|ADD_INPUT_LISTENER|add_input_listener|handle(DOMElement):handle|{ const el = elements[handle]; if(el) el.dataset.i = handle; }
dom|event|CHANGE|handle(DOMElement):handle string:value
dom|command|ADD_CHANGE_LISTENER|add_change_listener|handle(DOMElement):handle|{ const el = elements[handle]; if(el) el.dataset.g = handle; }
dom|event|KEYDOWN|handle(DOMElement):handle int32:keycode
dom|command|ADD_KEYDOWN_LISTENER|add_keydown_listener|handle(DOMElement):handle|{ const el = elements[handle]; if(el) el.dataset.k = handle; }
dom|command|REQUEST_FULLSCREEN|request_fullscreen|handle(DOMElement):handle|{ const el = elements[handle] || document.body; el.requestFullscreen().catch(console.error); }
dom|command|REQUEST_POINTER_LOCK|request_pointer_lock|handle(DOMElement):handle|{ const el = elements[handle] || document.body; el.requestPointerLock(); }
dom|command|SCROLL_TO_TOP|scroll_to_top||{ window.scrollTo({ top: 0, left: 0, behavior: 'instant' }); }

# ------------------------------------------------------------------------------
# CANVAS 2D
# ------------------------------------------------------------------------------
canvas|command|CREATE_CANVAS|create_canvas|string:dom_id float64:width float64:height RET:handle(Canvas)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const c = document.createElement('canvas'); c.id = dom_id; c.width = width; c.height = height; elements[dom_id] = c; elements[handle] = c; return handle; }
canvas|command|GET_CONTEXT_2D|get_context_2d|handle(Canvas):canvas_handle RET:handle(CanvasContext2D)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const c = elements[canvas_handle]; if(!c) { console.warn('get_context_2d: unknown canvas', canvas_handle); return -1; } contexts[handle] = c.getContext('2d'); return handle; }
canvas|command|GET_CONTEXT_WEBGL|get_context_webgl|handle(Canvas):canvas_handle RET:handle(WebGLContext)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const c = elements[canvas_handle]; if(!c) { console.warn('get_context_webgl: unknown canvas', canvas_handle); return -1; } contexts[handle] = c.getContext('webgl'); return handle; }
canvas|command|GET_CONTEXT_WEBGPU|get_context_webgpu|handle(Canvas):canvas_handle RET:handle(WGPUContext)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const c = elements[canvas_handle]; if(!c) { console.warn('get_context_webgpu: unknown canvas', canvas_handle); return -1; } contexts[handle] = c.getContext('webgpu'); return handle; }
canvas|command|SET_SIZE|set_size|handle(Canvas):handle float64:width float64:height|{ const c = elements[handle]; if(c) { c.width = width; c.height = height; } }
canvas|command|SET_FILL_STYLE|set_fill_style|handle(CanvasContext2D):handle uint8:r uint8:g uint8:b|{ const ctx = contexts[handle]; if(!ctx){ console.warn('set_fill_style: unknown context', handle); continue; } ctx.fillStyle = `rgb(${r},${g},${b})`; }
canvas|command|SET_FILL_STYLE_STR|set_fill_style_str|handle(CanvasContext2D):handle string:color|{ const ctx = contexts[handle]; if(ctx) ctx.fillStyle = color; }
canvas|command|FILL_RECT|fill_rect|handle(CanvasContext2D):handle float64:x float64:y float64:w float64:h|{ const ctx = contexts[handle]; if(!ctx){ console.warn('fill_rect: unknown context', handle); continue; } ctx.fillRect(x, y, w, h); }
canvas|command|CLEAR_RECT|clear_rect|handle(CanvasContext2D):handle float64:x float64:y float64:w float64:h|{ const ctx = contexts[handle]; if(!ctx){ console.warn('clear_canvas: unknown context', handle); continue; } ctx.clearRect(x, y, w, h); }
canvas|command|STROKE_RECT|stroke_rect|handle(CanvasContext2D):handle float64:x float64:y float64:w float64:h|{ const ctx = contexts[handle]; if(!ctx){ console.warn('stroke_rect: unknown context', handle); continue; } ctx.strokeRect(x, y, w, h); }
canvas|command|SET_STROKE_STYLE|set_stroke_style|handle(CanvasContext2D):handle uint8:r uint8:g uint8:b|{ const ctx = contexts[handle]; if(!ctx){ console.warn('set_stroke_style: unknown context', handle); continue; } ctx.strokeStyle = `rgb(${r},${g},${b})`; }
canvas|command|SET_STROKE_STYLE_STR|set_stroke_style_str|handle(CanvasContext2D):handle string:color|{ const ctx = contexts[handle]; if(ctx) ctx.strokeStyle = color; }
canvas|command|SET_LINE_WIDTH|set_line_width|handle(CanvasContext2D):handle float64:width|{ const ctx = contexts[handle]; if(ctx) ctx.lineWidth = width; }
canvas|command|BEGIN_PATH|begin_path|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(!ctx){ console.warn('begin_path: unknown context', handle); continue; } ctx.beginPath(); }
canvas|command|CLOSE_PATH|close_path|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(ctx) ctx.closePath(); }
canvas|command|MOVE_TO|move_to|handle(CanvasContext2D):handle float64:x float64:y|{ const ctx = contexts[handle]; if(!ctx){ console.warn('move_to: unknown context', handle); continue; } ctx.moveTo(x, y); }
canvas|command|LINE_TO|line_to|handle(CanvasContext2D):handle float64:x float64:y|{ const ctx = contexts[handle]; if(!ctx){ console.warn('line_to: unknown context', handle); continue; } ctx.lineTo(x, y); }
canvas|command|STROKE|stroke|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(!ctx){ console.warn('stroke: unknown context', handle); continue; } ctx.stroke(); }
canvas|command|FILL|fill|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(!ctx){ console.warn('fill: unknown context', handle); continue; } ctx.fill(); }
canvas|command|ARC|arc|handle(CanvasContext2D):handle float64:x float64:y float64:radius float64:start_angle float64:end_angle|{ const ctx = contexts[handle]; if(!ctx){ console.warn('arc: unknown context', handle); continue; } ctx.arc(x, y, radius, start_angle, end_angle); }
canvas|command|FILL_TEXT|fill_text|handle(CanvasContext2D):handle string:text float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.fillText(text, x, y); }
canvas|command|FILL_TEXT_F|fill_text_f|handle(CanvasContext2D):handle string:fmt float64:val float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.fillText(fmt.replace('%f', val.toFixed(2)), x, y); }
canvas|command|FILL_TEXT_I|fill_text_i|handle(CanvasContext2D):handle string:fmt int32:val float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.fillText(fmt.replace('%d', val), x, y); }
canvas|command|SET_FONT|set_font|handle(CanvasContext2D):handle string:font|{ const ctx = contexts[handle]; if(ctx) ctx.font = font; }
canvas|command|SET_TEXT_ALIGN|set_text_align|handle(CanvasContext2D):handle string:align|{ const ctx = contexts[handle]; if(ctx) ctx.textAlign = align; }
canvas|command|DRAW_IMAGE|draw_image|handle(CanvasContext2D):handle handle(Image):img_handle float64:x float64:y|{ const ctx = contexts[handle]; const img = images[img_handle]; if(ctx && img) ctx.drawImage(img, x, y); }
canvas|command|TRANSLATE|translate|handle(CanvasContext2D):handle float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.translate(x, y); }
canvas|command|ROTATE|rotate|handle(CanvasContext2D):handle float64:angle|{ const ctx = contexts[handle]; if(ctx) ctx.rotate(angle); }
canvas|command|SCALE|scale|handle(CanvasContext2D):handle float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.scale(x, y); }
canvas|command|SAVE|save|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(ctx) ctx.save(); }
canvas|command|RESTORE|restore|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(ctx) ctx.restore(); }
canvas|command|LOG_CANVAS_INFO|log_canvas_info|handle(Canvas):handle|{ const cv = elements[handle]; if(!cv){ console.warn('log_canvas_info: unknown canvas handle', handle); continue; } console.log('Canvas', handle, 'size:', cv.width, 'x', cv.height); }
canvas|command|SET_GLOBAL_ALPHA|set_global_alpha|handle(CanvasContext2D):handle float64:alpha|{ const ctx = contexts[handle]; if(ctx) ctx.globalAlpha = alpha; }
canvas|command|SET_LINE_CAP|set_line_cap|handle(CanvasContext2D):handle string:cap|{ const ctx = contexts[handle]; if(ctx) ctx.lineCap = cap; }
canvas|command|SET_LINE_JOIN|set_line_join|handle(CanvasContext2D):handle string:join|{ const ctx = contexts[handle]; if(ctx) ctx.lineJoin = join; }
canvas|command|SET_SHADOW|set_shadow|handle(CanvasContext2D):handle float64:blur float64:off_x float64:off_y string:color|{ const ctx = contexts[handle]; if(ctx) { ctx.shadowBlur = blur; ctx.shadowOffsetX = off_x; ctx.shadowOffsetY = off_y; ctx.shadowColor = color; } }
canvas|command|BEZIER_CURVE_TO|bezier_curve_to|handle(CanvasContext2D):handle float64:cp1x float64:cp1y float64:cp2x float64:cp2y float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); }
canvas|command|QUADRATIC_CURVE_TO|quadratic_curve_to|handle(CanvasContext2D):handle float64:cpx float64:cpy float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.quadraticCurveTo(cpx, cpy, x, y); }
canvas|command|RECT|rect|handle(CanvasContext2D):handle float64:x float64:y float64:w float64:h|{ const ctx = contexts[handle]; if(ctx) ctx.rect(x, y, w, h); }
canvas|command|CLIP|clip|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(ctx) ctx.clip(); }
canvas|command|STROKE_TEXT|stroke_text|handle(CanvasContext2D):handle string:text float64:x float64:y|{ const ctx = contexts[handle]; if(ctx) ctx.strokeText(text, x, y); }
canvas|command|SET_TEXT_BASELINE|set_text_baseline|handle(CanvasContext2D):handle string:baseline|{ const ctx = contexts[handle]; if(ctx) ctx.textBaseline = baseline; }
canvas|command|SET_GLOBAL_COMPOSITE_OPERATION|set_global_composite_operation|handle(CanvasContext2D):handle string:op|{ const ctx = contexts[handle]; if(ctx) ctx.globalCompositeOperation = op; }
canvas|command|DRAW_IMAGE_SCALED|draw_image_scaled|handle(CanvasContext2D):handle handle(Image):img_handle float64:x float64:y float64:w float64:h|{ const ctx = contexts[handle]; const img = images[img_handle]; if(ctx && img) ctx.drawImage(img, x, y, w, h); }
canvas|command|DRAW_IMAGE_FULL|draw_image_full|handle(CanvasContext2D):handle handle(Image):img_handle float64:sx float64:sy float64:sw float64:sh float64:dx float64:dy float64:dw float64:dh|{ const ctx = contexts[handle]; const img = images[img_handle]; if(ctx && img) ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); }
canvas|command|RESET_TRANSFORM|reset_transform|handle(CanvasContext2D):handle|{ const ctx = contexts[handle]; if(ctx) ctx.resetTransform(); }
canvas|command|ELLIPSE|ellipse|handle(CanvasContext2D):handle float64:x float64:y float64:radius_x float64:radius_y float64:rotation float64:start_angle float64:end_angle uint8:counter_clockwise|{ const ctx = contexts[handle]; if(ctx) ctx.ellipse(x, y, radius_x, radius_y, rotation, start_angle, end_angle, counter_clockwise !== 0); }
canvas|command|ARC_TO|arc_to|handle(CanvasContext2D):handle float64:x1 float64:y1 float64:x2 float64:y2 float64:radius|{ const ctx = contexts[handle]; if(ctx) ctx.arcTo(x1, y1, x2, y2, radius); }
canvas|command|SET_TRANSFORM|set_transform|handle(CanvasContext2D):handle float64:a float64:b float64:c float64:d float64:e float64:f|{ const ctx = contexts[handle]; if(ctx) ctx.setTransform(a, b, c, d, e, f); }
canvas|command|TRANSFORM|transform|handle(CanvasContext2D):handle float64:a float64:b float64:c float64:d float64:e float64:f|{ const ctx = contexts[handle]; if(ctx) ctx.transform(a, b, c, d, e, f); }
canvas|command|SET_MITER_LIMIT|set_miter_limit|handle(CanvasContext2D):handle float64:limit|{ const ctx = contexts[handle]; if(ctx) ctx.miterLimit = limit; }
canvas|command|SET_IMAGE_SMOOTHING_ENABLED|set_image_smoothing_enabled|handle(CanvasContext2D):handle uint8:enabled|{ const ctx = contexts[handle]; if(ctx) ctx.imageSmoothingEnabled = (enabled !== 0); }
canvas|command|MEASURE_TEXT_WIDTH|measure_text_width|handle(CanvasContext2D):handle string:text RET:float64|{ const ctx = contexts[handle]; return (ctx ? ctx.measureText(text).width : 0); }

# ------------------------------------------------------------------------------
# INPUT
# ------------------------------------------------------------------------------
input|event|KEY_DOWN|int32:key_code
input|event|KEY_UP|int32:key_code
input|event|MOUSE_DOWN|int32:button int32:x int32:y
input|event|MOUSE_UP|int32:button int32:x int32:y
input|event|MOUSE_MOVE|int32:x int32:y
input|command|INIT_KEYBOARD|init_keyboard||{ window.addEventListener('keydown', e => { push_event_input_KEY_DOWN(e.keyCode); _triggerDiscreteUpdate(); }); window.addEventListener('keyup', e => { push_event_input_KEY_UP(e.keyCode); _triggerDiscreteUpdate(); }); }
input|command|INIT_MOUSE|init_mouse|handle(DOMElement):handle|{ const el = elements[handle] || document; el.addEventListener('mousedown', e => { push_event_input_MOUSE_DOWN(e.button, e.offsetX, e.offsetY); _triggerDiscreteUpdate(); }); el.addEventListener('mouseup', e => { push_event_input_MOUSE_UP(e.button, e.offsetX, e.offsetY); _triggerDiscreteUpdate(); }); el.addEventListener('mousemove', e => push_event_input_MOUSE_MOVE(e.offsetX, e.offsetY)); }
input|command|EXIT_POINTER_LOCK|exit_pointer_lock||{ document.exitPointerLock(); }

# ------------------------------------------------------------------------------
# SYSTEM
# ------------------------------------------------------------------------------
system|command|LOG|log|string:msg|{ console.log(msg); }
system|command|WARN|warn|string:msg|{ console.warn(msg); }
system|command|ERROR|error|string:msg|{ console.error(msg); }
system|command|SET_MAIN_LOOP|set_main_loop|func_ptr:func|{ const fn = table.get(func); if(!fn){ console.error('set_main_loop: function not found in table', func); continue; } _updateFn = fn; const loop = (t) => { fn(t); requestAnimationFrame(loop); }; requestAnimationFrame(loop); }
system|command|SET_TITLE|set_title|string:title|{ document.title = title; }
system|command|RELOAD|reload||{ location.reload(); }
system|command|OPEN_URL|open_url|string:url|{ window.open(url, '_blank'); }
system|command|GET_TIME|get_time|RET:float64|{ return performance.now(); }
system|command|GET_DATE_NOW|get_date_now|RET:float64|{ return Date.now(); }
system|command|GET_TIMEZONE_OFFSET_MS|get_timezone_offset_ms|RET:float64|{ return -new Date().getTimezoneOffset()*60000; }
system|command|GET_PATHNAME|get_pathname|RET:string|{ const ret = window.location.pathname || "/"; }
system|command|GET_SEARCH|get_search|RET:string|{ const ret = window.location.search || ""; }
system|command|GET_QUERY_PARAM|get_query_param|string:name RET:string|{ const ret = new URLSearchParams(window.location.search).get(name) || ""; }
system|command|GET_VISIBILITY_STATE|get_visibility_state|RET:string|{ const ret = document.visibilityState || 'visible'; }
system|command|IS_HIDDEN|is_hidden|RET:uint8|{ return document.hidden ? 1 : 0; }
system|command|PUSH_STATE|push_state|string:path|{ history.pushState(null, '', path); }
system|event|POPSTATE|string:path
system|command|INIT_POPSTATE|init_popstate||{ window.addEventListener('popstate', () => push_event_system_POPSTATE(window.location.pathname || '/')); }
system|event|VISIBILITY_CHANGE|uint8:hidden string:state
system|command|INIT_VISIBILITY_CHANGE|init_visibility_change||{ document.addEventListener('visibilitychange', () => push_event_system_VISIBILITY_CHANGE(document.hidden ? 1 : 0, document.visibilityState || 'visible')); }

# ------------------------------------------------------------------------------
# STORAGE (LocalStorage)
# ------------------------------------------------------------------------------
storage|command|SET_ITEM|set_item|string:key string:value|{ localStorage.setItem(key, value); }
storage|command|GET_ITEM|get_item|string:key RET:string|{ const ret = localStorage.getItem(key) || ""; }
storage|command|REMOVE_ITEM|remove_item|string:key|{ localStorage.removeItem(key); }
storage|command|CLEAR|clear||{ localStorage.clear(); }

# ------------------------------------------------------------------------------
# AUDIO
# ------------------------------------------------------------------------------
audio|command|CREATE_AUDIO|create_audio|string:src RET:handle(Audio)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const a = new Audio(src); audios[handle] = a; elements[handle] = a; return handle; }
audio|command|PLAY|play|handle(Audio):handle|{ const a = audios[handle]; if(a) a.play().catch(e => console.warn(e)); }
audio|command|PAUSE|pause|handle(Audio):handle|{ const a = audios[handle]; if(a) a.pause(); }
audio|command|SET_VOLUME|set_volume|handle(Audio):handle float64:vol|{ const a = audios[handle]; if(a) a.volume = vol; }
audio|command|SET_LOOP|set_loop|handle(Audio):handle uint8:loop|{ const a = audios[handle]; if(a) a.loop = (loop !== 0); }
audio|command|GET_CURRENT_TIME|get_current_time|handle(Audio):handle RET:float64|{ const a = audios[handle]; return (a ? (a.currentTime || 0) : 0); }
audio|command|GET_DURATION|get_duration|handle(Audio):handle RET:float64|{ const a = audios[handle]; return (a ? (a.duration || 0) : 0); }

# ------------------------------------------------------------------------------
# WEBSOCKETS
# ------------------------------------------------------------------------------
websocket|event|MESSAGE|handle(WebSocket):handle string:data
websocket|event|OPEN|handle(WebSocket):handle
websocket|event|CLOSE|handle(WebSocket):handle
websocket|event|ERROR|handle(WebSocket):handle
websocket|command|CONNECT|connect|string:url RET:handle(WebSocket)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const ws = new WebSocket(url); websockets[handle] = ws; ws.onmessage = (e) => push_event_websocket_MESSAGE(handle, e.data); ws.onopen = () => push_event_websocket_OPEN(handle); ws.onclose = () => { push_event_websocket_CLOSE(handle); websockets[handle] = undefined; }; ws.onerror = () => { push_event_websocket_ERROR(handle); websockets[handle] = undefined; }; return handle; }
websocket|command|SEND|send|handle(WebSocket):handle string:msg|{ const ws = websockets[handle]; if(ws && ws.readyState === 1) ws.send(msg); }
websocket|command|CLOSE|close|handle(WebSocket):handle|{ const ws = websockets[handle]; if(ws) { ws.close(); websockets[handle] = undefined; } }

# ------------------------------------------------------------------------------
# FETCH
# ------------------------------------------------------------------------------
fetch|event|SUCCESS|handle(FetchRequest):id string:data
fetch|event|ERROR|handle(FetchRequest):id string:error
fetch|command|GET|get|string:url string:headers RET:handle(FetchRequest)|{ const id = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); let h = {}; try { h = JSON.parse(headers); } catch(e) {} fetch(url, { headers: h }).then(r => r.text().then(d => ({ ok: r.ok, status: r.status, statusText: r.statusText, data: d }))).then(res => { if(res.ok) push_event_fetch_SUCCESS(id, res.data); else push_event_fetch_ERROR(id, res.data && res.data.length ? res.data : (res.status + ' ' + res.statusText)); }).catch(e => push_event_fetch_ERROR(id, e.toString())); return id; }
fetch|command|POST|post|string:url string:body string:headers RET:handle(FetchRequest)|{ const id = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); let h = {}; try { h = JSON.parse(headers); } catch(e) {} fetch(url, { method: 'POST', body: body, headers: h }).then(r => r.text().then(d => ({ ok: r.ok, status: r.status, statusText: r.statusText, data: d }))).then(res => { if(res.ok) push_event_fetch_SUCCESS(id, res.data); else push_event_fetch_ERROR(id, res.data && res.data.length ? res.data : (res.status + ' ' + res.statusText)); }).catch(e => push_event_fetch_ERROR(id, e.toString())); return id; }
fetch|command|PATCH|patch|string:url string:body string:headers RET:handle(FetchRequest)|{ const id = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); let h = {}; try { h = JSON.parse(headers); } catch(e) {} fetch(url, { method: 'PATCH', body: body, headers: h }).then(r => r.text().then(d => ({ ok: r.ok, status: r.status, statusText: r.statusText, data: d }))).then(res => { if(res.ok) push_event_fetch_SUCCESS(id, res.data); else push_event_fetch_ERROR(id, res.data && res.data.length ? res.data : (res.status + ' ' + res.statusText)); }).catch(e => push_event_fetch_ERROR(id, e.toString())); return id; }

# ------------------------------------------------------------------------------
# IMAGES
# ------------------------------------------------------------------------------
image|command|LOAD|load|string:src RET:handle(Image)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const img = new Image(); img.src = src; images[handle] = img; elements[handle] = img; return handle; }

# ------------------------------------------------------------------------------
# WEBGL
# ------------------------------------------------------------------------------
webgl|command|VIEWPORT|viewport|handle(WebGLContext):ctx_handle int32:x int32:y int32:width int32:height|{ const gl = contexts[ctx_handle]; if(gl) gl.viewport(x, y, width, height); }
webgl|command|CLEAR_COLOR|clear_color|handle(WebGLContext):ctx_handle float64:r float64:g float64:b float64:a|{ const gl = contexts[ctx_handle]; if(gl) gl.clearColor(r, g, b, a); }
webgl|command|CLEAR|clear|handle(WebGLContext):ctx_handle uint32:mask|{ const gl = contexts[ctx_handle]; if(gl) gl.clear(mask); }
webgl|command|CREATE_SHADER|create_shader|handle(WebGLContext):ctx_handle uint32:type string:source RET:handle(WebGLShader)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const gl = contexts[ctx_handle]; if(gl) { const s = gl.createShader(type); gl.shaderSource(s, source); gl.compileShader(s); if(!gl.getShaderParameter(s, gl.COMPILE_STATUS)) console.error(gl.getShaderInfoLog(s)); webgl_shaders[handle] = s; } return handle; }
webgl|command|CREATE_PROGRAM|create_program|handle(WebGLContext):ctx_handle RET:handle(WebGLProgram)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const gl = contexts[ctx_handle]; if(gl) { const p = gl.createProgram(); webgl_programs[handle] = p; } return handle; }
webgl|command|ATTACH_SHADER|attach_shader|handle(WebGLContext):ctx_handle handle(WebGLProgram):prog_handle handle(WebGLShader):shader_handle|{ const gl = contexts[ctx_handle]; const p = webgl_programs[prog_handle]; const s = webgl_shaders[shader_handle]; if(gl && p && s) gl.attachShader(p, s); }
webgl|command|LINK_PROGRAM|link_program|handle(WebGLContext):ctx_handle handle(WebGLProgram):prog_handle|{ const gl = contexts[ctx_handle]; const p = webgl_programs[prog_handle]; if(gl && p) { gl.linkProgram(p); if(!gl.getProgramParameter(p, gl.LINK_STATUS)) console.error(gl.getProgramInfoLog(p)); } }
webgl|command|BIND_ATTRIB_LOCATION|bind_attrib_location|handle(WebGLContext):ctx_handle handle(WebGLProgram):prog_handle uint32:index string:name|{ const gl = contexts[ctx_handle]; const p = webgl_programs[prog_handle]; if(gl && p) gl.bindAttribLocation(p, index, name); }
webgl|command|USE_PROGRAM|use_program|handle(WebGLContext):ctx_handle handle(WebGLProgram):prog_handle|{ const gl = contexts[ctx_handle]; const p = webgl_programs[prog_handle]; if(gl && p) gl.useProgram(p); }
webgl|command|CREATE_BUFFER|create_buffer|handle(WebGLContext):ctx_handle RET:handle(WebGLBuffer)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const gl = contexts[ctx_handle]; if(gl) { const b = gl.createBuffer(); webgl_buffers[handle] = b; } return handle; }
webgl|command|BIND_BUFFER|bind_buffer|handle(WebGLContext):ctx_handle uint32:target handle(WebGLBuffer):buf_handle|{ const gl = contexts[ctx_handle]; const b = webgl_buffers[buf_handle]; if(gl && b) gl.bindBuffer(target, b); }
webgl|command|BUFFER_DATA|buffer_data|handle(WebGLContext):ctx_handle uint32:target uint32:data_ptr uint32:data_len uint32:usage|{ const gl = contexts[ctx_handle]; if(gl) { const data = new Uint8Array(memory.buffer, data_ptr, data_len); gl.bufferData(target, data, usage); } }
webgl|command|ENABLE_VERTEX_ATTRIB_ARRAY|enable_vertex_attrib_array|handle(WebGLContext):ctx_handle uint32:index|{ const gl = contexts[ctx_handle]; if(gl) gl.enableVertexAttribArray(index); }
webgl|command|ENABLE|enable|handle(WebGLContext):ctx_handle uint32:cap|{ const gl = contexts[ctx_handle]; if(gl) gl.enable(cap); }
webgl|command|GET_UNIFORM_LOCATION|get_uniform_location|handle(WebGLContext):ctx_handle handle(WebGLProgram):prog_handle string:name RET:handle(WebGLUniform)|{ const handle = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); const gl = contexts[ctx_handle]; const p = webgl_programs[prog_handle]; if(gl && p) { const loc = gl.getUniformLocation(p, name); if(!loc) console.warn('getUniformLocation failed:', name); webgl_uniforms[handle] = loc; } return handle; }
webgl|command|UNIFORM_1F|uniform_1f|handle(WebGLContext):ctx_handle handle(WebGLUniform):loc_handle float64:val|{ const gl = contexts[ctx_handle]; const loc = webgl_uniforms[loc_handle]; if(loc === undefined) console.warn('uniform_1f: loc undefined', loc_handle); if(gl && loc !== undefined) gl.uniform1f(loc, val); }
webgl|command|VERTEX_ATTRIB_POINTER|vertex_attrib_pointer|handle(WebGLContext):ctx_handle uint32:index int32:size uint32:type uint8:normalized int32:stride int32:offset|{ const gl = contexts[ctx_handle]; if(gl) gl.vertexAttribPointer(index, size, type, normalized !== 0, stride, offset); }
webgl|command|DRAW_ARRAYS|draw_arrays|handle(WebGLContext):ctx_handle uint32:mode int32:first int32:count|{ const gl = contexts[ctx_handle]; if(gl) gl.drawArrays(mode, first, count); }

# ------------------------------------------------------------------------------
# WEBGPU
# ------------------------------------------------------------------------------
wgpu|event|ADAPTER_READY|handle(WGPUAdapter):handle
wgpu|event|DEVICE_READY|handle(WGPUDevice):handle
wgpu|command|REQUEST_ADAPTER|request_adapter||{ if (!navigator.gpu) { console.warn('NO: navigator.gpu is undefined — WebGPU not available'); push_event_wgpu_ADAPTER_READY(-1); return; } console.log('navigator.gpu OK'); navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }).then(a => a || navigator.gpu.requestAdapter()).then(adapter => { if (!adapter) { console.warn('NO: requestAdapter returned null — no usable adapter'); push_event_wgpu_ADAPTER_READY(-1); return; } console.log('Adapter:', adapter); console.log('Features:', Array.from(adapter.features || [])); console.log('Limits:', adapter.limits || {}); const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_adapters[h] = adapter; push_event_wgpu_ADAPTER_READY(h); }).catch(e => { console.error('requestAdapter failed:', e); push_event_wgpu_ADAPTER_READY(-1); }); }
wgpu|command|REQUEST_DEVICE|request_device|handle(WGPUAdapter):adapter_handle|{ const a = webgpu_adapters[adapter_handle]; if(a) a.requestDevice().then(d => { const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_devices[h] = d; webgpu_queues[h] = d.queue; push_event_wgpu_DEVICE_READY(h); }).catch(e => console.error("WebGPU: requestDevice failed", e)); }
wgpu|command|GET_QUEUE|get_queue|handle(WGPUDevice):device_handle RET:handle(WGPUQueue)|{ const d = webgpu_devices[device_handle]; if(!d) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_queues[h] = d.queue; return h; }
wgpu|command|CREATE_SHADER_MODULE|create_shader_module|handle(WGPUDevice):device_handle string:code RET:handle(WGPUShaderModule)|{ const d = webgpu_devices[device_handle]; if(!d) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_shaders[h] = d.createShaderModule({ code: code }); return h; }
wgpu|command|CREATE_COMMAND_ENCODER|create_command_encoder|handle(WGPUDevice):device_handle RET:handle(WGPUCommandEncoder)|{ const d = webgpu_devices[device_handle]; if(!d) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_encoders[h] = d.createCommandEncoder(); return h; }
wgpu|command|CONFIGURE|configure|handle(WGPUContext):context_handle handle(WGPUDevice):device_handle string:format|{ const ctx = contexts[context_handle]; const dev = webgpu_devices[device_handle]; if(ctx && dev) ctx.configure({ device: dev, format: format === 'preferred' ? navigator.gpu.getPreferredCanvasFormat() : format, alphaMode: 'premultiplied' }); }
wgpu|command|GET_CURRENT_TEXTURE_VIEW|get_current_texture_view|handle(WGPUContext):context_handle RET:handle(WGPUTextureView)|{ const ctx = contexts[context_handle]; if(!ctx) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_views[h] = ctx.getCurrentTexture().createView(); return h; }
wgpu|command|BEGIN_RENDER_PASS|begin_render_pass|handle(WGPUCommandEncoder):encoder_handle handle(WGPUTextureView):view_handle float64:r float64:g float64:b float64:a RET:handle(WGPURenderPass)|{ const enc = webgpu_encoders[encoder_handle]; const view = webgpu_views[view_handle]; if(!enc || !view) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_passes[h] = enc.beginRenderPass({ colorAttachments: [{ view: view, clearValue: {r, g, b, a}, loadOp: 'clear', storeOp: 'store' }] }); return h; }
wgpu|command|END_PASS|end_pass|handle(WGPURenderPass):pass_handle|{ const pass = webgpu_passes[pass_handle]; if(pass) pass.end(); }
wgpu|command|FINISH_ENCODER|finish_encoder|handle(WGPUCommandEncoder):encoder_handle RET:handle(WGPUCommandBuffer)|{ const enc = webgpu_encoders[encoder_handle]; if(!enc) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_buffers[h] = enc.finish(); return h; }
wgpu|command|QUEUE_SUBMIT|queue_submit|handle(WGPUQueue):queue_handle handle(WGPUCommandBuffer):command_buffer_handle|{ const q = webgpu_queues[queue_handle]; const cb = webgpu_buffers[command_buffer_handle]; if(q && cb) q.submit([cb]); }
wgpu|command|CREATE_RENDER_PIPELINE_SIMPLE|create_render_pipeline_simple|handle(WGPUDevice):device_handle handle(WGPUShaderModule):vs_module_handle handle(WGPUShaderModule):fs_module_handle string:vs_entry string:fs_entry string:format RET:handle(WGPURenderPipeline)|{ const d = webgpu_devices[device_handle]; const vs = webgpu_shaders[vs_module_handle]; const fs = webgpu_shaders[fs_module_handle]; if(!d || !vs || !fs) return -1; const h = (window.webcc_next_id = (window.webcc_next_id || 0) + 1); webgpu_pipelines[h] = d.createRenderPipeline({ layout: 'auto', vertex: { module: vs, entryPoint: vs_entry }, fragment: { module: fs, entryPoint: fs_entry, targets: [{ format: format === 'preferred' ? navigator.gpu.getPreferredCanvasFormat() : format }] }, primitive: { topology: 'triangle-list' } }); return h; }
wgpu|command|SET_PIPELINE|set_pipeline|handle(WGPURenderPass):pass_handle handle(WGPURenderPipeline):pipeline_handle|{ const pass = webgpu_passes[pass_handle]; const pipe = webgpu_pipelines[pipeline_handle]; if(pass && pipe) pass.setPipeline(pipe); }
wgpu|command|DRAW|draw|handle(WGPURenderPass):pass_handle int32:vertex_count int32:instance_count int32:first_vertex int32:first_instance|{ const pass = webgpu_passes[pass_handle]; if(pass) pass.draw(vertex_count, instance_count, first_vertex, first_instance); }
