Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
63 kB · 1117 lines
C++
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118#include <algorithm>#include <array>#include <cmath>#include <cstdint>#include <cstring>#include <memory>#include <vector>#include <string>#include "QuickJsEngine.hpp"#include "../runtime/include/ac/theme_assets.hpp"extern "C" {#include "third_party/quickjs-ng/quickjs.h"}
#include <stdexcept>
namespace ac::xbox {namespace {struct RetainedMesh; // ScenePrimitives.inc// Meshes the piece uploads once and draws by handle; owned by the piece, so// they live exactly as long as its JS context.struct SceneStore { std::vector<std::shared_ptr<RetainedMesh>> meshes; };struct CallScope { Api* api; SceneStore scene; };#include "ScenePrimitives.inc"
bool ValidOskiewarMatchId(std::string_view value) { if (value.substr(0, 3) != "ow-") return false; if (value.size() == 23) { for (std::size_t index = 3; index < value.size(); ++index) { const auto character = value[index]; if (!((character >= 'a' && character <= 'z') || character == '-')) return false; } return value[9] == '-' && value[16] == '-'; } const auto name = value.substr(3); std::size_t digit = 0; while (digit < name.size() && name[digit] >= 'a' && name[digit] <= 'z') ++digit; if (digit < 4 || digit > 7 || name.size() - digit < 1 || name.size() - digit > 3) return false; for (; digit < name.size(); ++digit) if (name[digit] < '0' || name[digit] > '9') return false; return true;}
JSValue Wipe(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); int32_t r = 0, g = 0, b = 0; if (!scope || !scope->api || argc < 3 || JS_ToInt32(context, &r, argv[0]) || JS_ToInt32(context, &g, argv[1]) || JS_ToInt32(context, &b, argv[2])) return JS_EXCEPTION; scope->api->graphics.wipe({static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}); return JS_UNDEFINED;}
JSValue AccountState(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); const auto json = scope && scope->api && scope->api->account_state ? scope->api->account_state() : std::string("{\"status\":\"unavailable\"}"); return JS_ParseJSON(context, json.data(), json.size(), "account-state");}JSValue AccountAction(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->account_action || argc < 1) return JS_FALSE; const char* raw = JS_ToCString(context, argv[0]); if (!raw) return JS_EXCEPTION; const std::string action(raw); JS_FreeCString(context, raw); if (action != "login" && action != "logout" && action != "cancel" && action != "leaderboard") return JS_ThrowRangeError(context, "unknown account action"); std::string payload; if(argc>1) {const char* rawPayload=JS_ToCString(context,argv[1]); if(!rawPayload)return JS_EXCEPTION;payload=rawPayload;JS_FreeCString(context,rawPayload);} if(payload.size()>256)return JS_ThrowRangeError(context,"account payload too large"); scope->api->account_action(action,payload); return JS_TRUE;}
JSValue AccountReport(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope=static_cast<CallScope*>(JS_GetContextOpaque(context)); if(!scope||!scope->api||!scope->api->account_report||argc<1)return JS_FALSE; const char* raw=JS_ToCString(context,argv[0]);if(!raw)return JS_EXCEPTION; const std::string payload(raw);JS_FreeCString(context,raw); if(payload.size()>4096)return JS_ThrowRangeError(context,"account report too large"); return JS_NewBool(context,scope->api->account_report(payload));}
JSValue Synth(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double frequency = 440.0, duration = .05; if (!scope || !scope->api || argc < 1 || JS_ToFloat64(context, &frequency, argv[0])) return JS_EXCEPTION; if (argc > 1) JS_ToFloat64(context, &duration, argv[1]); SynthVoice voice; voice.frequency_hz = static_cast<float>(frequency); voice.duration_s = static_cast<float>(duration); scope->api->sound.synth(voice); return JS_UNDEFINED;}
JSValue Drum(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* name = JS_ToCString(context, argv[0]); if (!name) return JS_EXCEPTION; double velocity = 1.0, pan = 0.0; if (argc > 1 && JS_ToFloat64(context, &velocity, argv[1])) { JS_FreeCString(context, name); return JS_EXCEPTION; } if (argc > 2 && JS_ToFloat64(context, &pan, argv[2])) { JS_FreeCString(context, name); return JS_EXCEPTION; } const std::string drum(name); JS_FreeCString(context, name); static constexpr std::string_view allowed[] = {"kick", "snare", "clap", "hat", "block", "bell", "whoosh", "glass", "glass-shard", "gunshot", "smg-shot", "bass", "pad-0", "pad-1", "pad-2", "pad-3"}; if (std::find(std::begin(allowed), std::end(allowed), drum) == std::end(allowed)) return JS_ThrowRangeError(context, "unknown drum"); scope->api->sound.drum(drum, static_cast<float>(velocity), static_cast<float>(pan)); return JS_UNDEFINED;}
JSValue Oscillator(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double frequency = 220.0, volume = .12; if (!scope || !scope->api || argc < 1 || JS_ToFloat64(context, &frequency, argv[0])) return JS_EXCEPTION; if (argc > 1 && JS_ToFloat64(context, &volume, argv[1])) return JS_EXCEPTION; scope->api->sound.oscillator(static_cast<float>(frequency), static_cast<float>(volume)); return JS_UNDEFINED;}
JSValue SkateAudio(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double speed = 0, volume = 0; if (!scope || !scope->api || argc < 2 || JS_ToFloat64(context, &speed, argv[0]) || JS_ToFloat64(context, &volume, argv[1])) return JS_EXCEPTION; if (!std::isfinite(speed) || !std::isfinite(volume)) return JS_ThrowRangeError(context, "skateAudio requires finite values"); scope->api->sound.skate_audio(static_cast<float>(std::max(0.0, std::min(1.0, speed))), static_cast<float>(std::max(0.0, std::min(.5, volume)))); return JS_UNDEFINED;}
JSValue OscillatorStop(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; scope->api->sound.oscillator_stop(); return JS_UNDEFINED;}
JSValue Write(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* value = JS_ToCString(context, argv[0]); if (!value) return JS_EXCEPTION; double x = 80, y = 80, size = 64; if (argc > 1) JS_ToFloat64(context, &x, argv[1]); if (argc > 2) JS_ToFloat64(context, &y, argv[2]); if (argc > 3) JS_ToFloat64(context, &size, argv[3]); int32_t r = 255, g = 255, b = 255; if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.write({value, static_cast<float>(x), static_cast<float>(y), static_cast<float>(size), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); JS_FreeCString(context, value); return JS_UNDEFINED;}
JSValue Box(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x = 0, y = 0, width = 0, height = 0; int32_t r = 255, g = 255, b = 255; if (!scope || !scope->api || argc < 4 || JS_ToFloat64(context, &x, argv[0]) || JS_ToFloat64(context, &y, argv[1]) || JS_ToFloat64(context, &width, argv[2]) || JS_ToFloat64(context, &height, argv[3])) return JS_EXCEPTION; if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.box({static_cast<float>(x), static_cast<float>(y), static_cast<float>(width), static_cast<float>(height), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
JSValue Line(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x1 = 0, y1 = 0, x2 = 0, y2 = 0, width = 1; int32_t r = 255, g = 255, b = 255; if (!scope || !scope->api || argc < 4 || JS_ToFloat64(context, &x1, argv[0]) || JS_ToFloat64(context, &y1, argv[1]) || JS_ToFloat64(context, &x2, argv[2]) || JS_ToFloat64(context, &y2, argv[3])) return JS_EXCEPTION; if (argc > 4) JS_ToFloat64(context, &width, argv[4]); if (argc > 5) JS_ToInt32(context, &r, argv[5]); if (argc > 6) JS_ToInt32(context, &g, argv[6]); if (argc > 7) JS_ToInt32(context, &b, argv[7]); scope->api->graphics.line({static_cast<float>(x1), static_cast<float>(y1), static_cast<float>(x2), static_cast<float>(y2), static_cast<float>(width), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
JSValue TriangleFill(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0; int32_t r = 255, g = 255, b = 255; if (!scope || !scope->api || argc < 6 || JS_ToFloat64(context, &x1, argv[0]) || JS_ToFloat64(context, &y1, argv[1]) || JS_ToFloat64(context, &x2, argv[2]) || JS_ToFloat64(context, &y2, argv[3]) || JS_ToFloat64(context, &x3, argv[4]) || JS_ToFloat64(context, &y3, argv[5])) return JS_EXCEPTION; if (argc > 6) JS_ToInt32(context, &r, argv[6]); if (argc > 7) JS_ToInt32(context, &g, argv[7]); if (argc > 8) JS_ToInt32(context, &b, argv[8]); const std::array<double, 6> coordinates{x1, y1, x2, y2, x3, y3}; if (!std::all_of(coordinates.begin(), coordinates.end(), [](double value) { return std::isfinite(value) && std::abs(value) <= 32768.0; })) return JS_ThrowRangeError(context, "invalid triangle coordinates"); scope->api->graphics.triangle({static_cast<float>(x1), static_cast<float>(y1), 0, static_cast<float>(x2), static_cast<float>(y2), 0, static_cast<float>(x3), static_cast<float>(y3), 0, {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
JSValue Triangle3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); std::array<double, 9> value{}; if (!scope || !scope->api || argc < 9) return JS_EXCEPTION; for (int index = 0; index < 9; ++index) if (JS_ToFloat64(context, &value[index], argv[index])) return JS_EXCEPTION; if (!std::all_of(value.begin(), value.end(), [](double item) { return std::isfinite(item) && std::abs(item) <= 32768.0; })) return JS_ThrowRangeError(context, "invalid 3D triangle coordinates"); int32_t r = 255, g = 255, b = 255; if (argc > 9) JS_ToInt32(context, &r, argv[9]); if (argc > 10) JS_ToInt32(context, &g, argv[10]); if (argc > 11) JS_ToInt32(context, &b, argv[11]); scope->api->graphics.triangle({static_cast<float>(value[0]), static_cast<float>(value[1]), static_cast<float>(value[2]), static_cast<float>(value[3]), static_cast<float>(value[4]), static_cast<float>(value[5]), static_cast<float>(value[6]), static_cast<float>(value[7]), static_cast<float>(value[8]), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
// One native boundary crossing for an entire frame's triangle stream. The// Float32Array layout repeats x1,y1,z1,x2,y2,z2,x3,y3,z3,r,g,b. Keeping this// API deliberately flat lets QuickJS fill a reusable buffer without allocating// thousands of argument arrays or calling into C++ once per face.JSValue Triangles3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_ThrowTypeError(context, "triangles3d requires a Float32Array"); if (JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_FLOAT32) return JS_ThrowTypeError(context, "triangles3d requires a Float32Array");
size_t byteOffset = 0, byteLength = 0, bytesPerElement = 0; JSValue arrayBuffer = JS_GetTypedArrayBuffer( context, argv[0], &byteOffset, &byteLength, &bytesPerElement); if (JS_IsException(arrayBuffer)) return arrayBuffer; size_t bufferLength = 0; uint8_t* bytes = JS_GetArrayBuffer(context, &bufferLength, arrayBuffer); if (!bytes || bytesPerElement != sizeof(float) || byteOffset > bufferLength || byteLength > bufferLength - byteOffset || byteLength % (12 * sizeof(float)) != 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "triangles3d length must be a multiple of 12 floats"); } const size_t capacity = byteLength / (12 * sizeof(float)); uint32_t requestedCount = static_cast<uint32_t>(capacity); if (argc > 1 && JS_ToUint32(context, &requestedCount, argv[1])) { JS_FreeValue(context, arrayBuffer); return JS_EXCEPTION; } const size_t count = requestedCount; if (count > capacity) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "triangles3d count exceeds the buffer"); } if (count > 8192) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "triangles3d accepts at most 8192 triangles"); }
const float* values = reinterpret_cast<const float*>(bytes + byteOffset); for (size_t triangle = 0; triangle < count; ++triangle) { const float* value = values + triangle * 12; bool valid = true; for (int index = 0; index < 9; ++index) valid = valid && std::isfinite(value[index]) && std::abs(value[index]) <= 32768.f; if (!valid) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "invalid 3D triangle coordinates"); } const auto color = [](float channel) { return static_cast<uint8_t>((std::max)(0.f, (std::min)(255.f, channel))); }; scope->api->graphics.triangle({value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], {color(value[9]), color(value[10]), color(value[11]), 255}}); } JS_FreeValue(context, arrayBuffer); return JS_NewInt32(context, static_cast<int32_t>(count));}
// Screen-facing point-sampled sprites. Layout repeats x,y,z,size,r,g,b,frame.JSValue Sprites3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1 || JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_FLOAT32) return JS_ThrowTypeError(context, "sprites3d requires a Float32Array"); size_t byteOffset = 0, byteLength = 0, bytesPerElement = 0; JSValue arrayBuffer = JS_GetTypedArrayBuffer( context, argv[0], &byteOffset, &byteLength, &bytesPerElement); if (JS_IsException(arrayBuffer)) return arrayBuffer; size_t bufferLength = 0; uint8_t* bytes = JS_GetArrayBuffer(context, &bufferLength, arrayBuffer); if (!bytes || bytesPerElement != sizeof(float) || byteOffset > bufferLength || byteLength > bufferLength - byteOffset || byteLength % (8 * sizeof(float)) != 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "sprites3d length must be a multiple of 8 floats"); } const size_t capacity = byteLength / (8 * sizeof(float)); uint32_t requestedCount = static_cast<uint32_t>(capacity); if (argc > 1 && JS_ToUint32(context, &requestedCount, argv[1])) { JS_FreeValue(context, arrayBuffer); return JS_EXCEPTION; } if (requestedCount > capacity || requestedCount > 512) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "sprites3d count exceeds the buffer or 512 sprites"); } const float* values = reinterpret_cast<const float*>(bytes + byteOffset); const auto color = [](float channel) { return static_cast<uint8_t>((std::max)(0.f, (std::min)(255.f, channel))); }; for (size_t sprite = 0; sprite < requestedCount; ++sprite) { const float* value = values + sprite * 8; bool valid = true; for (int index = 0; index < 4; ++index) valid = valid && std::isfinite(value[index]) && std::abs(value[index]) <= 32768.f; if (!valid || value[3] <= 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "invalid sprite coordinates or size"); } scope->api->graphics.sprite({value[0], value[1], value[2], value[3], {color(value[4]), color(value[5]), color(value[6]), 255}, static_cast<uint8_t>((std::max)(0.f, (std::min)(1.f, value[7])))}); } JS_FreeValue(context, arrayBuffer); return JS_NewInt32(context, static_cast<int32_t>(requestedCount));}
JSValue ThemeReady(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); return JS_NewBool(context, scope && scope->api && scope->api->graphics.theme_ready());}
JSValue DecalClear(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope=static_cast<CallScope*>(JS_GetContextOpaque(context)); return JS_NewBool(context,scope&&scope->api&&scope->api->graphics.decal_clear());}JSValue DecalStamp(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope=static_cast<CallScope*>(JS_GetContextOpaque(context)); if(!scope||!scope->api||argc<12)return JS_ThrowTypeError(context,"decalStamp requires 12 coordinates"); std::array<float,12> q{}; for(int i=0;i<12;i++){double n=0;if(JS_ToFloat64(context,&n,argv[i])||!std::isfinite(n)||std::abs(n)>32768)return JS_ThrowRangeError(context,"invalid decal stamp");q[i]=float(n);} if(q[0]<0||q[1]<0||q[2]<=0||q[3]<=0||q[0]+q[2]>256||q[1]+q[3]>256)return JS_ThrowRangeError(context,"decal stamp outside atlas"); return JS_NewBool(context,scope->api->graphics.decal_stamp(q));}
JSValue DecalTint(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope=static_cast<CallScope*>(JS_GetContextOpaque(context)); if(!scope||!scope->api||argc!=15)return JS_ThrowTypeError(context,"decalTint requires 12 coordinates and RGB"); std::array<float,12> q{};std::array<float,3> tint{}; for(int i=0;i<15;i++){double n=0;if(JS_ToFloat64(context,&n,argv[i])||!std::isfinite(n)||std::abs(n)>32768)return JS_ThrowRangeError(context,"invalid tinted stamp");if(i<12)q[i]=float(n);else {if(n<0||n>255)return JS_ThrowRangeError(context,"invalid tint");tint[i-12]=float(n);}} return JS_NewBool(context,scope->api->graphics.decal_tint(q,tint));}
JSValue ThemeAssetReady(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); int32_t asset = -1; if (argc < 1 || JS_ToInt32(context, &asset, argv[0])) return JS_FALSE; return JS_NewBool(context, scope && scope->api && asset >= 0 && asset < static_cast<int>(theme_assets.size()) && scope->api->graphics.theme_asset_ready(asset));}
JSValue ThemeSpriteDraw(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 12) return JS_ThrowTypeError(context, "themeSprite requires 12 arguments"); int32_t asset = 0; if (JS_ToInt32(context, &asset, argv[0])) return JS_EXCEPTION; if (asset < 0 || asset >= static_cast<int>(theme_assets.size())) return JS_ThrowRangeError(context, "invalid theme asset"); double v[10]{}; for (int i = 0; i < 9; ++i) { if (JS_ToFloat64(context, &v[i], argv[i + 1])) return JS_EXCEPTION; } if (JS_ToFloat64(context, &v[9], argv[11])) return JS_EXCEPTION; for (double value : v) if (!std::isfinite(value) || std::abs(value) > 32768) return JS_ThrowRangeError(context, "invalid theme coordinates"); const double sourceWidth = theme_assets[asset].master_width; const double sourceHeight = theme_assets[asset].master_height; if (v[0] < 0 || v[1] < 0 || v[2] <= 0 || v[3] <= 0 || v[0] + v[2] > sourceWidth || v[1] + v[3] > sourceHeight || v[6] <= 0 || v[7] <= 0 || v[9] < -1.5 || v[9] > 1.5) return JS_ThrowRangeError(context, "theme rectangle outside bounds"); if (!scope->api->graphics.theme_asset_ready(asset)) return JS_FALSE; scope->api->graphics.theme_sprite({asset, static_cast<float>(v[0]), static_cast<float>(v[1]), static_cast<float>(v[2]), static_cast<float>(v[3]), static_cast<float>(v[4]), static_cast<float>(v[5]), static_cast<float>(v[6]), static_cast<float>(v[7]), static_cast<float>(v[8]), static_cast<float>(v[9]), JS_ToBool(context, argv[10]) > 0, argc < 13 || JS_ToBool(context, argv[12]) > 0}); return JS_TRUE;}
JSValue ThemeQuadDraw(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 17) return JS_ThrowTypeError(context, "themeQuad requires 17 arguments"); int32_t asset = 0; if (JS_ToInt32(context, &asset, argv[0])) return JS_EXCEPTION; if (asset < 0 || asset >= static_cast<int>(theme_assets.size())) return JS_ThrowRangeError(context, "invalid theme asset"); float v[16]{}; for (int i = 0; i < 16; ++i) { double number = 0; if (JS_ToFloat64(context, &number, argv[i + 1])) return JS_EXCEPTION; if (!std::isfinite(number) || std::abs(number) > 32768) return JS_ThrowRangeError(context, "invalid theme quad coordinates"); v[i] = static_cast<float>(number); } if (v[0] < 0 || v[1] < 0 || v[2] <= 0 || v[3] <= 0 || v[0] + v[2] > theme_assets[asset].master_width || v[1] + v[3] > theme_assets[asset].master_height) return JS_ThrowRangeError(context, "theme rectangle outside bounds"); for (int i = 6; i < 16; i += 3) if (v[i] < -1.5 || v[i] > 1.5) return JS_ThrowRangeError(context, "invalid theme quad depth"); if (!scope->api->graphics.theme_asset_ready(asset)) return JS_FALSE; scope->api->graphics.theme_quad({asset, v[0],v[1],v[2],v[3], v[4],v[5],v[6], v[7],v[8],v[9], v[10],v[11],v[12],v[13],v[14],v[15], false, argc < 18 || JS_ToBool(context, argv[17]) != 0}); return JS_TRUE;}
// UV-mapped triangle stream. Layout repeats three x,y,z,u,v vertices followed// by an RGB light tint: 18 floats per triangle.JSValue TexturedTriangles3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1 || JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_FLOAT32) return JS_ThrowTypeError(context, "texturedTriangles3d requires a Float32Array"); size_t byteOffset = 0, byteLength = 0, bytesPerElement = 0; JSValue arrayBuffer = JS_GetTypedArrayBuffer( context, argv[0], &byteOffset, &byteLength, &bytesPerElement); if (JS_IsException(arrayBuffer)) return arrayBuffer; size_t bufferLength = 0; uint8_t* bytes = JS_GetArrayBuffer(context, &bufferLength, arrayBuffer); if (!bytes || bytesPerElement != sizeof(float) || byteOffset > bufferLength || byteLength > bufferLength - byteOffset || byteLength % (18 * sizeof(float)) != 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "texturedTriangles3d length must be a multiple of 18 floats"); } const size_t capacity = byteLength / (18 * sizeof(float)); uint32_t requestedCount = static_cast<uint32_t>(capacity); if (argc > 1 && JS_ToUint32(context, &requestedCount, argv[1])) { JS_FreeValue(context, arrayBuffer); return JS_EXCEPTION; } if (requestedCount > capacity || requestedCount > 2048) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "texturedTriangles3d count exceeds the buffer or 2048 triangles"); } const float* values = reinterpret_cast<const float*>(bytes + byteOffset); const auto color = [](float channel) { return static_cast<uint8_t>((std::max)(0.f, (std::min)(255.f, channel))); }; for (size_t triangle = 0; triangle < requestedCount; ++triangle) { const float* value = values + triangle * 18; bool valid = true; for (int vertex = 0; vertex < 3; ++vertex) { const int base = vertex * 5; for (int index = 0; index < 5; ++index) valid = valid && std::isfinite(value[base + index]) && std::abs(value[base + index]) <= 32768.f; } if (!valid) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "invalid textured triangle coordinates"); } scope->api->graphics.textured_triangle({ value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], {color(value[15]), color(value[16]), color(value[17]), 255}}); } JS_FreeValue(context, arrayBuffer); return JS_NewInt32(context, static_cast<int32_t>(requestedCount));}
JSValue QueueSystemWrite(JSContext* context, int argc, JSValueConst* argv, const char* family) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* value = JS_ToCString(context, argv[0]); if (!value) return JS_EXCEPTION; double x = 80, y = 80, size = 48; int32_t r = 255, g = 255, b = 255; if (argc > 1) JS_ToFloat64(context, &x, argv[1]); if (argc > 2) JS_ToFloat64(context, &y, argv[2]); if (argc > 3) JS_ToFloat64(context, &size, argv[3]); if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.system_write({value, family, static_cast<float>(x), static_cast<float>(y), static_cast<float>(size), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); JS_FreeCString(context, value); return JS_UNDEFINED;}
JSValue SystemWrite(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { return QueueSystemWrite(context, argc, argv, "Segoe UI");}
JSValue YwftWrite(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { return QueueSystemWrite(context, argc, argv, "YWFT Processing");}
JSValue ComicWrite(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { return QueueSystemWrite(context, argc, argv, "Comic Relief");}
JSValue SystemGlyph(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* name = JS_ToCString(context, argv[0]); if (!name) return JS_EXCEPTION; double x = 80, y = 80, size = 64; int32_t r = 255, g = 255, b = 255; if (argc > 1) JS_ToFloat64(context, &x, argv[1]); if (argc > 2) JS_ToFloat64(context, &y, argv[2]); if (argc > 3) JS_ToFloat64(context, &size, argv[3]); if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.system_glyph({name, static_cast<float>(x), static_cast<float>(y), static_cast<float>(size), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); JS_FreeCString(context, name); return JS_UNDEFINED;}
JSValue Painting(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x = 0, y = 0, width = 640, height = 480; if (!scope || !scope->api) return JS_EXCEPTION; if (argc > 0) JS_ToFloat64(context, &x, argv[0]); if (argc > 1) JS_ToFloat64(context, &y, argv[1]); if (argc > 2) JS_ToFloat64(context, &width, argv[2]); if (argc > 3) JS_ToFloat64(context, &height, argv[3]); scope->api->graphics.image({"latest-painting", static_cast<float>(x), static_cast<float>(y), static_cast<float>(width), static_cast<float>(height)}); return JS_UNDEFINED;}
JSValue StampPainting(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 3) return JS_EXCEPTION; const char* source = JS_ToCString(context, argv[0]); if (!source) return JS_EXCEPTION; std::string code(source); JS_FreeCString(context, source); const bool valid = code.size() >= 2 && code.size() <= 9 && code.front() == '#' && std::all_of(code.begin() + 1, code.end(), [](unsigned char character) { return (character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z'); }); if (!valid) return JS_ThrowTypeError(context, "painting code must match #[0-9A-Za-z]{1,8}"); double x = 0, y = 0, scale = 1; if (JS_ToFloat64(context, &x, argv[1]) || JS_ToFloat64(context, &y, argv[2]) || (argc > 3 && JS_ToFloat64(context, &scale, argv[3]))) return JS_EXCEPTION; if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(scale) || std::abs(scale) > 8.0) return JS_ThrowRangeError(context, "invalid painting transform"); scope->api->graphics.image({code, static_cast<float>(x), static_cast<float>(y), 0, 0, static_cast<float>(scale), true}); return JS_UNDEFINED;}
JSValue Blur(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); int32_t radius = 0; if (!scope || !scope->api || argc < 1 || JS_ToInt32(context, &radius, argv[0])) return JS_EXCEPTION; if (radius < 0 || radius > 16) return JS_ThrowRangeError(context, "blur radius must be between 0 and 16"); scope->api->graphics.blur(static_cast<unsigned>(radius)); return JS_UNDEFINED;}
JSValue Telemetry(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->telemetry || argc < 1) return JS_UNDEFINED; const char* event = JS_ToCString(context, argv[0]); const char* detail = argc > 1 ? JS_ToCString(context, argv[1]) : nullptr; if (!event) return JS_EXCEPTION; std::string line = "JS "; line += event; if (detail) { line += " "; line += detail; } scope->api->telemetry(line); if (detail) JS_FreeCString(context, detail); JS_FreeCString(context, event); return JS_UNDEFINED;}
JSValue GameSignal(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->game_signal || argc < 1) return JS_UNDEFINED; const char* rawEvent = JS_ToCString(context, argv[0]); if (!rawEvent) return JS_EXCEPTION; std::string event(rawEvent); JS_FreeCString(context, rawEvent); if (event.empty() || event.size() > 32 || !std::all_of(event.begin(), event.end(), [](unsigned char character) { return std::isalnum(character) || character == '_' || character == '-'; })) return JS_ThrowRangeError(context, "invalid game signal event"); int32_t player = -1; double value = 0, value2 = 0; if ((argc > 1 && JS_ToInt32(context, &player, argv[1])) || (argc > 2 && JS_ToFloat64(context, &value, argv[2])) || (argc > 3 && JS_ToFloat64(context, &value2, argv[3]))) return JS_EXCEPTION; if (player < -1 || player > 3 || !std::isfinite(value) || !std::isfinite(value2) || std::abs(value) > 1000000 || std::abs(value2) > 1000000) return JS_ThrowRangeError(context, "invalid game signal payload"); scope->api->game_signal(event, player, static_cast<float>(value), static_cast<float>(value2)); return JS_UNDEFINED;}
JSValue SaveReplay(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->replay_save || argc < 1) return JS_UNDEFINED; size_t length = 0; const char* raw = JS_ToCStringLen(context, &length, argv[0]); if (!raw) return JS_EXCEPTION; if (length < 2 || length > 524288 || raw[0] != '{') { JS_FreeCString(context, raw); return JS_ThrowRangeError(context, "replay must be a JSON object under 512 KiB"); } scope->api->replay_save(std::string_view(raw, length)); JS_FreeCString(context, raw); return JS_UNDEFINED;}
JSValue PublishLive(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->live_publish || argc < 2) return JS_UNDEFINED; size_t matchLength = 0, stateLength = 0; const char* rawMatch = JS_ToCStringLen(context, &matchLength, argv[0]); if (!rawMatch) return JS_EXCEPTION; const char* rawState = JS_ToCStringLen(context, &stateLength, argv[1]); if (!rawState) { JS_FreeCString(context, rawMatch); return JS_EXCEPTION; } const bool valid = ValidOskiewarMatchId( std::string_view(rawMatch, matchLength)) && stateLength >= 2 && stateLength <= 7168 && rawState[0] == '{'; if (!valid) { JS_FreeCString(context, rawState); JS_FreeCString(context, rawMatch); return JS_ThrowRangeError(context, "invalid oskiewar live payload"); } scope->api->live_publish(std::string_view(rawMatch, matchLength), std::string_view(rawState, stateLength)); JS_FreeCString(context, rawState); JS_FreeCString(context, rawMatch); return JS_UNDEFINED;}
JSValue OskiewarNetSend(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->net_send || argc < 2) return JS_NewBool(context, false); size_t ml = 0, pl = 0; const char* match = JS_ToCStringLen(context, &ml, argv[0]); if (!match) return JS_EXCEPTION; const char* packet = JS_ToCStringLen(context, &pl, argv[1]); if (!packet) { JS_FreeCString(context, match); return JS_EXCEPTION; } const bool valid = ValidOskiewarMatchId(std::string_view(match, ml)) && pl >= 2 && pl <= 7168 && packet[0] == '{'; const bool sent = valid && scope->api->net_send( std::string_view(match, ml), std::string_view(packet, pl)); JS_FreeCString(context, match); JS_FreeCString(context, packet); return JS_NewBool(context, sent);}
JSValue OskiewarNetPoll(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); JSValue result = JS_NewArray(context); if (!scope || !scope->api || !scope->api->net_poll) return result; uint32_t index = 0; for (const auto& packet : scope->api->net_poll()) { if (index >= 64) break; if (packet.size() > 7168) continue; JSValue value = JS_ParseJSON(context, packet.data(), packet.size(), "oskiewar-net"); if (JS_IsException(value)) { JS_FreeValue(context, JS_GetException(context)); continue; } JS_SetPropertyUint32(context, result, index++, value); } return result;}
// postEffects(focusY, band, feather, tiltPx, motionX, motionY). The shader// keeps the HUD depth sharp; this only steers the world's blur for the frame.JSValue PostEffectsSet(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 6) return JS_ThrowTypeError(context, "postEffects requires 6 numbers"); double v[6]{}; for (int i = 0; i < 6; ++i) if (JS_ToFloat64(context, &v[i], argv[i]) || !std::isfinite(v[i])) return JS_ThrowRangeError(context, "postEffects values must be finite"); auto& post = scope->api->post_effects; post.focus_y = static_cast<float>(std::clamp(v[0], 0.0, 1.0)); post.band = static_cast<float>(std::clamp(v[1], 0.0, 1.0)); post.feather = static_cast<float>(std::clamp(v[2], 0.01, 1.0)); post.tilt_px = static_cast<float>(std::clamp(v[3], 0.0, 16.0)); post.motion_x = static_cast<float>(std::clamp(v[4], -48.0, 48.0)); post.motion_y = static_cast<float>(std::clamp(v[5], -48.0, 48.0)); return JS_TRUE;}
JSValue RuntimeInfo(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; JSValue result = JS_NewObject(context); JS_SetPropertyStr(context, result, "width", JS_NewInt32(context, scope->api->screen.width)); JS_SetPropertyStr(context, result, "height", JS_NewInt32(context, scope->api->screen.height)); JS_SetPropertyStr(context, result, "renderWidth", JS_NewInt32(context, scope->api->system.render_width)); JS_SetPropertyStr(context, result, "renderHeight", JS_NewInt32(context, scope->api->system.render_height)); JS_SetPropertyStr(context, result, "refreshHz", JS_NewFloat64(context, scope->api->system.refresh_hz)); JS_SetPropertyStr(context, result, "frameMs", JS_NewFloat64(context, scope->api->system.frame_ms)); JS_SetPropertyStr(context, result, "renderCpuMs", JS_NewFloat64(context, scope->api->system.render_cpu_ms)); JS_SetPropertyStr(context, result, "presentMs", JS_NewFloat64(context, scope->api->system.present_ms)); JS_SetPropertyStr(context, result, "antialiasingSamples", JS_NewInt32(context, scope->api->system.antialiasing_samples)); JS_SetPropertyStr(context, result, "antialiasingMode", JS_NewString(context, scope->api->system.antialiasing_mode.c_str())); JS_SetPropertyStr(context, result, "sampleRate", JS_NewInt32(context, scope->api->sound.sample_rate())); JS_SetPropertyStr(context, result, "simCount", JS_NewInt64(context, scope->api->sim_count)); JS_SetPropertyStr(context, result, "paintCount", JS_NewInt64(context, scope->api->paint_count)); JS_SetPropertyStr(context, result, "monotonicUs", JS_NewInt64(context, scope->api->clock.monotonic_us)); JS_SetPropertyStr(context, result, "unixMs", JS_NewInt64(context, scope->api->clock.unix_ms)); JS_SetPropertyStr(context, result, "clockSynced", JS_NewBool(context, scope->api->clock.network_synced)); JS_SetPropertyStr(context, result, "clockOffsetMs", JS_NewInt64(context, scope->api->clock.network_offset_ms)); JS_SetPropertyStr(context, result, "clockRttMs", JS_NewInt32(context, scope->api->clock.network_rtt_ms)); JS_SetPropertyStr(context, result, "clockSyncAgeMs", JS_NewInt64(context, scope->api->clock.network_sync_age_ms)); JS_SetPropertyStr(context, result, "audioLatencyMs", JS_NewFloat64(context, scope->api->audio.output_latency_ms)); JS_SetPropertyStr(context, result, "audioSubmitUs", JS_NewFloat64(context, scope->api->audio.submit_us)); JS_SetPropertyStr(context, result, "inputToAudioUs", JS_NewFloat64(context, scope->api->audio.input_to_submit_us)); JS_SetPropertyStr(context, result, "audioGlitches", JS_NewInt32(context, scope->api->audio.glitches)); JS_SetPropertyStr(context, result, "midiInputs", JS_NewInt32(context, scope->api->audio.midi_inputs)); JS_SetPropertyStr(context, result, "midiEvents", JS_NewInt64(context, scope->api->audio.midi_events)); JS_SetPropertyStr(context, result, "midiNote", JS_NewInt32(context, scope->api->audio.midi_note)); JS_SetPropertyStr(context, result, "midiVelocity", JS_NewInt32(context, scope->api->audio.midi_velocity)); JS_SetPropertyStr(context, result, "midiChannel", JS_NewInt32(context, scope->api->audio.midi_channel)); JS_SetPropertyStr(context, result, "midiPitchBend", JS_NewInt32(context, scope->api->audio.midi_pitch_bend)); JS_SetPropertyStr(context, result, "midiControl", JS_NewInt32(context, scope->api->audio.midi_control)); JS_SetPropertyStr(context, result, "midiControlValue", JS_NewInt32(context, scope->api->audio.midi_control_value)); JS_SetPropertyStr(context, result, "midiGate", JS_NewBool(context, scope->api->audio.midi_gate)); JS_SetPropertyStr(context, result, "midiStatus", JS_NewString(context, scope->api->audio.midi_status.c_str())); const auto clientErrorReportStatus = scope->api->client_error_report_status ? scope->api->client_error_report_status() : std::string{}; JS_SetPropertyStr(context, result, "clientErrorReportStatus", JS_NewString(context, clientErrorReportStatus.c_str())); return result;}
JSValue PadObject(JSContext* context, const PadState& pad, int index) { JSValue result = JS_NewObject(context); JS_SetPropertyStr(context, result, "index", JS_NewInt32(context, index)); JS_SetPropertyStr(context, result, "connected", JS_NewBool(context, pad.connected)); JS_SetPropertyStr(context, result, "leftX", JS_NewFloat64(context, pad.left_x)); JS_SetPropertyStr(context, result, "leftY", JS_NewFloat64(context, pad.left_y)); JS_SetPropertyStr(context, result, "rightX", JS_NewFloat64(context, pad.right_x)); JS_SetPropertyStr(context, result, "rightY", JS_NewFloat64(context, pad.right_y)); JS_SetPropertyStr(context, result, "leftTrigger", JS_NewFloat64(context, pad.left_trigger)); JS_SetPropertyStr(context, result, "rightTrigger", JS_NewFloat64(context, pad.right_trigger)); JSValue down = JS_NewArray(context); uint32_t downIndex = 0; for (const auto& button : pad.down) JS_SetPropertyUint32(context, down, downIndex++, JS_NewString(context, button.c_str())); JS_SetPropertyStr(context, result, "down", down); return result;}
JSValue GamepadState(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; if (argc < 1) return PadObject(context, scope->api->gamepad, 0); int32_t index = 0; if (JS_ToInt32(context, &index, argv[0])) return JS_EXCEPTION; if (index >= 0 && static_cast<std::size_t>(index) < scope->api->gamepad.pads.size()) return PadObject(context, scope->api->gamepad.pads[static_cast<std::size_t>(index)], index); return PadObject(context, PadState{}, index);}
JSValue Controllers(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; JSValue result = JS_NewArray(context); uint32_t index = 0; for (const auto& controller : scope->api->gamepad.controllers) { JSValue item = JS_NewObject(context); JS_SetPropertyStr(context, item, "id", JS_NewString(context, controller.id.c_str())); JS_SetPropertyStr(context, item, "name", JS_NewString(context, controller.name.c_str())); JS_SetPropertyStr(context, item, "vendorId", JS_NewInt32(context, controller.vendor_id)); JS_SetPropertyStr(context, item, "productId", JS_NewInt32(context, controller.product_id)); JS_SetPropertyStr(context, item, "axes", JS_NewInt32(context, controller.axes)); JS_SetPropertyStr(context, item, "buttons", JS_NewInt32(context, controller.buttons)); JS_SetPropertyStr(context, item, "switches", JS_NewInt32(context, controller.switches)); JS_SetPropertyStr(context, item, "gamepad", JS_NewBool(context, controller.gamepad)); JS_SetPropertyUint32(context, result, index++, item); } return result;}
JSValue Capabilities(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; JSValue result = JS_NewObject(context); JS_SetPropertyStr(context, result, "platform", JS_NewString(context, "xbox-uwp")); JS_SetPropertyStr(context, result, "inputFamily", JS_NewString(context, "xbox")); JS_SetPropertyStr(context, result, "quickjs", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "direct3d11", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "xaudio2", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "internetClient", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "privateNetworkClientServer", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "online", JS_NewBool(context, scope->api->system.online)); JS_SetPropertyStr(context, result, "networkLevel", JS_NewString(context, scope->api->system.network_level.c_str())); JS_SetPropertyStr(context, result, "networkName", JS_NewString(context, scope->api->system.network_name.c_str())); JS_SetPropertyStr(context, result, "version", JS_NewString(context, scope->api->system.version.c_str())); JS_SetPropertyStr(context, result, "deviceFamily", JS_NewString(context, scope->api->system.device_family.c_str())); JS_SetPropertyStr(context, result, "deviceFamilyVersion", JS_NewString(context, scope->api->system.device_family_version.c_str())); JS_SetPropertyStr(context, result, "productName", JS_NewString(context, scope->api->system.product_name.c_str())); JS_SetPropertyStr(context, result, "memoryUsageBytes", JS_NewInt64(context, scope->api->system.memory_usage_bytes)); JS_SetPropertyStr(context, result, "memoryLimitBytes", JS_NewInt64(context, scope->api->system.memory_limit_bytes)); JS_SetPropertyStr(context, result, "expectedMemoryLimitBytes", JS_NewInt64(context, scope->api->system.expected_memory_limit_bytes)); JS_SetPropertyStr(context, result, "width", JS_NewInt32(context, scope->api->screen.width)); JS_SetPropertyStr(context, result, "height", JS_NewInt32(context, scope->api->screen.height)); JS_SetPropertyStr(context, result, "renderWidth", JS_NewInt32(context, scope->api->system.render_width)); JS_SetPropertyStr(context, result, "renderHeight", JS_NewInt32(context, scope->api->system.render_height)); JS_SetPropertyStr(context, result, "refreshHz", JS_NewFloat64(context, scope->api->system.refresh_hz)); JS_SetPropertyStr(context, result, "frameMs", JS_NewFloat64(context, scope->api->system.frame_ms)); JS_SetPropertyStr(context, result, "renderCpuMs", JS_NewFloat64(context, scope->api->system.render_cpu_ms)); JS_SetPropertyStr(context, result, "presentMs", JS_NewFloat64(context, scope->api->system.present_ms)); JS_SetPropertyStr(context, result, "antialiasingSamples", JS_NewInt32(context, scope->api->system.antialiasing_samples)); JS_SetPropertyStr(context, result, "antialiasingMode", JS_NewString(context, scope->api->system.antialiasing_mode.c_str())); JS_SetPropertyStr(context, result, "liveLocalState", JS_NewBool(context, true)); // The scene API level the piece may assume (xbox/ENGINE.md). Additive only: // 1 = triangles3d/disc3d/capsule3d/sceneMesh, 2 = + postEffects and retained // meshes (meshUpload/meshDraw/meshFree), 3 = + raymarched sdfFigure. JS_SetPropertyStr(context, result, "sceneApi", JS_NewInt32(context, 3)); return result;}
JSValue AcData(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; const auto snapshot = std::atomic_load(&scope->api->ac); JSValue result = JS_NewObject(context); if (!snapshot) return result; JS_SetPropertyStr(context, result, "mood", JS_NewString(context, snapshot->mood.c_str())); JS_SetPropertyStr(context, result, "moodHandle", JS_NewString(context, snapshot->mood_handle.c_str())); JS_SetPropertyStr(context, result, "clockFrom", JS_NewString(context, snapshot->clock_from.c_str())); JS_SetPropertyStr(context, result, "clockText", JS_NewString(context, snapshot->clock_text.c_str())); JS_SetPropertyStr(context, result, "paintingUrl", JS_NewString(context, snapshot->painting_url.c_str())); JS_SetPropertyStr(context, result, "paintingHandle", JS_NewString(context, snapshot->painting_handle.c_str())); JS_SetPropertyStr(context, result, "status", JS_NewString(context, snapshot->status.c_str())); JS_SetPropertyStr(context, result, "refreshedUnixMs", JS_NewInt64(context, snapshot->refreshed_unix_ms)); JSValue fighters = JS_NewArray(context); for (uint32_t index = 0; index < snapshot->fighters.size(); ++index) { const auto& profile = snapshot->fighters[index]; JSValue fighter = JS_NewObject(context); JS_SetPropertyStr(context, fighter, "handle", JS_NewString(context, profile.handle.c_str())); JS_SetPropertyStr(context, fighter, "mood", JS_NewString(context, profile.mood.c_str())); JS_SetPropertyStr(context, fighter, "lastChat", JS_NewString(context, profile.last_chat.c_str())); JSValue colors = JS_NewArray(context); for (uint32_t colorIndex = 0; colorIndex < profile.colors.size(); ++colorIndex) { const auto& color = profile.colors[colorIndex]; JSValue value = JS_NewObject(context); JS_SetPropertyStr(context, value, "r", JS_NewInt32(context, color.r)); JS_SetPropertyStr(context, value, "g", JS_NewInt32(context, color.g)); JS_SetPropertyStr(context, value, "b", JS_NewInt32(context, color.b)); JS_SetPropertyUint32(context, colors, colorIndex, value); } JS_SetPropertyStr(context, fighter, "colors", colors); JS_SetPropertyUint32(context, fighters, index, fighter); } JS_SetPropertyStr(context, result, "fighters", fighters); return result;}
JSValue DiscState(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; const auto snapshot = std::atomic_load(&scope->api->disc.snapshot); JSValue result = JS_NewObject(context); if (!snapshot) return result; JS_SetPropertyStr(context, result, "status", JS_NewString(context, snapshot->status.c_str())); JS_SetPropertyStr(context, result, "volume", JS_NewString(context, snapshot->volume.c_str())); JS_SetPropertyStr(context, result, "name", JS_NewString(context, snapshot->name.c_str())); JS_SetPropertyStr(context, result, "count", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->count))); JS_SetPropertyStr(context, result, "index", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->index))); JS_SetPropertyStr(context, result, "width", JS_NewInt32(context, snapshot->width)); JS_SetPropertyStr(context, result, "height", JS_NewInt32(context, snapshot->height)); JS_SetPropertyStr(context, result, "currentReady", JS_NewBool(context, snapshot->current_ready)); JS_SetPropertyStr(context, result, "copyStatus", JS_NewString(context, snapshot->copy_status.c_str())); JS_SetPropertyStr(context, result, "copied", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->copied))); JS_SetPropertyStr(context, result, "copyFailed", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->copy_failed))); return result;}
JSValue DiscScan(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; if (!scope->api->disc.scan) return JS_NewBool(context, false); scope->api->disc.scan(); return JS_NewBool(context, true);}
JSValue DiscShow(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); std::int64_t index = 0; if (!scope || !scope->api || argc < 1 || JS_ToInt64(context, &index, argv[0])) return JS_EXCEPTION; if (!scope->api->disc.show) return JS_NewBool(context, false); scope->api->disc.show(index); return JS_NewBool(context, true);}
JSValue DiscCopy(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; if (!scope->api->disc.copy) return JS_NewBool(context, false); scope->api->disc.copy(); return JS_NewBool(context, true);}
JSValue DiscPhoto(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x = 0, y = 0, width = 1920, height = 1080; if (!scope || !scope->api) return JS_EXCEPTION; if (argc > 0 && JS_ToFloat64(context, &x, argv[0])) return JS_EXCEPTION; if (argc > 1 && JS_ToFloat64(context, &y, argv[1])) return JS_EXCEPTION; if (argc > 2 && JS_ToFloat64(context, &width, argv[2])) return JS_EXCEPTION; if (argc > 3 && JS_ToFloat64(context, &height, argv[3])) return JS_EXCEPTION; if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) || !std::isfinite(height) || width <= 0 || height <= 0 || width > 3840 || height > 2160) return JS_ThrowRangeError(context, "invalid disc photo rectangle"); scope->api->graphics.image({"disc-photo", static_cast<float>(x), static_cast<float>(y), static_cast<float>(width), static_cast<float>(height)}); return JS_UNDEFINED;}
class QuickJsPiece final : public JsPiece { public: QuickJsPiece(const PieceBundle& bundle, const JsLimits& limits, std::string& error) : slug_(bundle.slug), version_(bundle.version) { runtime_ = JS_NewRuntime(); if (!runtime_) { error = "JS_NewRuntime failed"; return; } JS_SetMemoryLimit(runtime_, limits.max_heap_bytes); JS_SetMaxStackSize(runtime_, 1024 * 1024); context_ = JS_NewContext(runtime_); if (!context_) { error = "JS_NewContext failed"; return; } JS_SetContextOpaque(context_, &scope_); JSValue global = JS_GetGlobalObject(context_); JS_SetPropertyStr(context_, global, "wipe", JS_NewCFunction(context_, Wipe, "wipe", 3)); JS_SetPropertyStr(context_, global, "synth", JS_NewCFunction(context_, Synth, "synth", 2)); JS_SetPropertyStr(context_, global, "drum", JS_NewCFunction(context_, Drum, "drum", 3)); JS_SetPropertyStr(context_, global, "oscillator", JS_NewCFunction(context_, Oscillator, "oscillator", 2)); JS_SetPropertyStr(context_, global, "skateAudio", JS_NewCFunction(context_, SkateAudio, "skateAudio", 2)); JS_SetPropertyStr(context_, global, "oscillatorStop", JS_NewCFunction(context_, OscillatorStop, "oscillatorStop", 0)); JS_SetPropertyStr(context_, global, "write", JS_NewCFunction(context_, Write, "write", 7)); JS_SetPropertyStr(context_, global, "box", JS_NewCFunction(context_, Box, "box", 7)); JS_SetPropertyStr(context_, global, "line", JS_NewCFunction(context_, Line, "line", 8)); JS_SetPropertyStr(context_, global, "triangle", JS_NewCFunction(context_, TriangleFill, "triangle", 9)); JS_SetPropertyStr(context_, global, "disc3d", JS_NewCFunction(context_, SceneDisc, "disc3d", 7)); JS_SetPropertyStr(context_, global, "capsule3d", JS_NewCFunction(context_, SceneCapsule, "capsule3d", 9)); JS_SetPropertyStr(context_, global, "sceneMesh", JS_NewCFunction(context_, SceneMesh, "sceneMesh", 3)); JS_SetPropertyStr(context_, global, "meshUpload", JS_NewCFunction(context_, MeshUpload, "meshUpload", 3)); JS_SetPropertyStr(context_, global, "meshDraw", JS_NewCFunction(context_, MeshDraw, "meshDraw", 7)); JS_SetPropertyStr(context_, global, "meshFree", JS_NewCFunction(context_, MeshFree, "meshFree", 1)); JS_SetPropertyStr(context_, global, "sdfFigure", JS_NewCFunction(context_, SdfFigure, "sdfFigure", 3)); JS_SetPropertyStr(context_, global, "decalSurfaceSize", JS_NewInt32(context_, 4096)); JS_SetPropertyStr(context_, global, "meshTransparency", JS_NewBool(context_, true)); JS_SetPropertyStr(context_, global, "triangle3d", JS_NewCFunction(context_, Triangle3d, "triangle3d", 12)); JS_SetPropertyStr(context_, global, "triangles3d", JS_NewCFunction(context_, Triangles3d, "triangles3d", 2)); JS_SetPropertyStr(context_, global, "sprites3d", JS_NewCFunction(context_, Sprites3d, "sprites3d", 2)); JS_SetPropertyStr(context_, global, "texturedTriangles3d", JS_NewCFunction(context_, TexturedTriangles3d, "texturedTriangles3d", 2)); JS_SetPropertyStr(context_, global, "accountState", JS_NewCFunction(context_, AccountState, "accountState", 0)); JS_SetPropertyStr(context_, global, "accountAction", JS_NewCFunction(context_, AccountAction, "accountAction", 2)); JS_SetPropertyStr(context_, global, "accountReport", JS_NewCFunction(context_, AccountReport, "accountReport", 1)); JS_SetPropertyStr(context_, global, "themeReady", JS_NewCFunction(context_, ThemeReady, "themeReady", 0)); JS_SetPropertyStr(context_, global, "themeAssetReady", JS_NewCFunction(context_, ThemeAssetReady, "themeAssetReady", 1)); JS_SetPropertyStr(context_, global, "themeSprite", JS_NewCFunction(context_, ThemeSpriteDraw, "themeSprite", 12)); JS_SetPropertyStr(context_, global, "themeQuad", JS_NewCFunction(context_, ThemeQuadDraw, "themeQuad", 17)); JS_SetPropertyStr(context_, global, "decalClear", JS_NewCFunction(context_, DecalClear, "decalClear", 0)); JS_SetPropertyStr(context_, global, "decalTint", JS_NewCFunction(context_, DecalTint, "decalTint", 15)); JS_SetPropertyStr(context_, global, "decalStamp", JS_NewCFunction(context_, DecalStamp, "decalStamp", 12)); JS_SetPropertyStr(context_, global, "decalMesh", JS_NewCFunction(context_, DecalMesh, "decalMesh", 6)); JS_SetPropertyStr(context_, global, "systemWrite", JS_NewCFunction(context_, SystemWrite, "systemWrite", 7)); JS_SetPropertyStr(context_, global, "ywftWrite", JS_NewCFunction(context_, YwftWrite, "ywftWrite", 7)); JS_SetPropertyStr(context_, global, "comicWrite", JS_NewCFunction(context_, ComicWrite, "comicWrite", 7)); JS_SetPropertyStr(context_, global, "systemGlyph", JS_NewCFunction(context_, SystemGlyph, "systemGlyph", 7)); JS_SetPropertyStr(context_, global, "painting", JS_NewCFunction(context_, Painting, "painting", 4)); JS_SetPropertyStr(context_, global, "stampPainting", JS_NewCFunction(context_, StampPainting, "stampPainting", 4)); JS_SetPropertyStr(context_, global, "blur", JS_NewCFunction(context_, Blur, "blur", 1)); JS_SetPropertyStr(context_, global, "telemetry", JS_NewCFunction(context_, Telemetry, "telemetry", 2)); JS_SetPropertyStr(context_, global, "gameSignal", JS_NewCFunction(context_, GameSignal, "gameSignal", 4)); JS_SetPropertyStr(context_, global, "saveReplay", JS_NewCFunction(context_, SaveReplay, "saveReplay", 1)); JS_SetPropertyStr(context_, global, "publishLive", JS_NewCFunction(context_, PublishLive, "publishLive", 2)); JS_SetPropertyStr(context_, global, "oskiewarNetSend", JS_NewCFunction(context_, OskiewarNetSend, "oskiewarNetSend", 2)); JS_SetPropertyStr(context_, global, "oskiewarNetPoll", JS_NewCFunction(context_, OskiewarNetPoll, "oskiewarNetPoll", 0)); JS_SetPropertyStr(context_, global, "runtime", JS_NewCFunction(context_, RuntimeInfo, "runtime", 0)); JS_SetPropertyStr(context_, global, "postEffects", JS_NewCFunction(context_, PostEffectsSet, "postEffects", 6)); JS_SetPropertyStr(context_, global, "gamepad", JS_NewCFunction(context_, GamepadState, "gamepad", 1)); JS_SetPropertyStr(context_, global, "controllers", JS_NewCFunction(context_, Controllers, "controllers", 0)); JS_SetPropertyStr(context_, global, "capabilities", JS_NewCFunction(context_, Capabilities, "capabilities", 0)); JS_SetPropertyStr(context_, global, "ac", JS_NewCFunction(context_, AcData, "ac", 0)); JS_SetPropertyStr(context_, global, "disc", JS_NewCFunction(context_, DiscState, "disc", 0)); JS_SetPropertyStr(context_, global, "discScan", JS_NewCFunction(context_, DiscScan, "discScan", 0)); JS_SetPropertyStr(context_, global, "discShow", JS_NewCFunction(context_, DiscShow, "discShow", 1)); JS_SetPropertyStr(context_, global, "discCopy", JS_NewCFunction(context_, DiscCopy, "discCopy", 0)); JS_SetPropertyStr(context_, global, "discPhoto", JS_NewCFunction(context_, DiscPhoto, "discPhoto", 4)); JS_FreeValue(context_, global); JSValue result = JS_Eval(context_, bundle.source.data(), bundle.source.size(), bundle.slug.c_str(), JS_EVAL_TYPE_GLOBAL); if (JS_IsException(result)) error = ExceptionText(); else valid_ = true; JS_FreeValue(context_, result); } ~QuickJsPiece() override { if (context_) JS_FreeContext(context_); if (runtime_) JS_FreeRuntime(runtime_); } bool valid() const { return valid_; } std::string_view slug() const noexcept override { return slug_; } std::string_view version() const noexcept override { return version_; } void boot(Api& api) override { Call("boot", api); } void sim(Api& api) override { Call("sim", api); } void paint(Api& api) override { Call("paint", api); } void leave(Api& api) override { Call("leave", api); } void act(Api& api, const Event& event) override { scope_.api = &api; JSValue global = JS_GetGlobalObject(context_); JSValue function = JS_GetPropertyStr(context_, global, "act"); if (JS_IsFunction(context_, function)) { JSValue argument = JS_NewString(context_, event.name.c_str()); JSValue result = JS_Call(context_, function, global, 1, &argument); JS_FreeValue(context_, argument); if (JS_IsException(result)) { JS_FreeValue(context_, result); JS_FreeValue(context_, function); JS_FreeValue(context_, global); throw std::runtime_error(ExceptionText()); } JS_FreeValue(context_, result); } JS_FreeValue(context_, function); JS_FreeValue(context_, global); scope_.api = nullptr; } private: void Call(const char* name, Api& api) { scope_.api = &api; JSValue global = JS_GetGlobalObject(context_); JSValue function = JS_GetPropertyStr(context_, global, name); if (JS_IsFunction(context_, function)) { JSValue result = JS_Call(context_, function, global, 0, nullptr); if (JS_IsException(result)) { JS_FreeValue(context_, result); JS_FreeValue(context_, function); JS_FreeValue(context_, global); throw std::runtime_error(ExceptionText()); } JS_FreeValue(context_, result); } JS_FreeValue(context_, function); JS_FreeValue(context_, global); scope_.api = nullptr; } std::string ExceptionText() { JSValue exception = JS_GetException(context_); const char* text = JS_ToCString(context_, exception); std::string result = text ? text : "JavaScript exception"; if (text) JS_FreeCString(context_, text); JS_FreeValue(context_, exception); return result; } std::string slug_, version_; JSRuntime* runtime_ = nullptr; JSContext* context_ = nullptr; CallScope scope_{nullptr}; bool valid_ = false;};}
std::unique_ptr<JsPiece> QuickJsEngine::compile(const PieceBundle& bundle, const JsLimits& limits, std::string& error) { auto piece = std::make_unique<QuickJsPiece>(bundle, limits, error); return piece->valid() ? std::move(piece) : nullptr;}} // namespace ac::xbox