Spaces:
Running
Running
Playable Echo Climb (round 0 and round 30) in the hero
Browse files- .gitattributes +1 -0
- index.html +16 -1
- media/play/engine/godot.audio.position.worklet.js +66 -0
- media/play/engine/godot.audio.worklet.js +213 -0
- media/play/engine/godot.js +0 -0
- media/play/engine/godot.wasm.gz +3 -0
- media/play/godot_gpt/platformer-echo-climb/r0/index.pck +0 -0
- media/play/godot_gpt/platformer-echo-climb/r30/index.pck +3 -0
- play.html +94 -0
- static/css/v1.css +36 -0
- static/data/play.json +31 -0
- static/js/v1.js +50 -0
.gitattributes
CHANGED
|
@@ -157,3 +157,4 @@ media/pairs/godot_qwen/tycoon-music-label/final.mp4 filter=lfs diff=lfs merge=lf
|
|
| 157 |
media/pairs/godot_qwen/tycoon-music-label/p0.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 158 |
media/pairs/godot_qwen/visualnovel-lastsignal/final.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 159 |
media/pairs/godot_qwen/visualnovel-lastsignal/p0.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 157 |
media/pairs/godot_qwen/tycoon-music-label/p0.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 158 |
media/pairs/godot_qwen/visualnovel-lastsignal/final.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 159 |
media/pairs/godot_qwen/visualnovel-lastsignal/p0.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 160 |
+
media/play/godot_gpt/platformer-echo-climb/r30/index.pck filter=lfs diff=lfs merge=lfs -text
|
index.html
CHANGED
|
@@ -28,7 +28,8 @@
|
|
| 28 |
<div class="hero-bg" aria-hidden="true">
|
| 29 |
<video id="hero-video" muted loop playsinline preload="auto" data-src="hero_evo.mp4" data-poster="hero_evo.webp"></video>
|
| 30 |
</div>
|
| 31 |
-
<div class="hero-inner">
|
|
|
|
| 32 |
<p class="eyebrow">Agentic verification · Progressive development</p>
|
| 33 |
<h1>EvoGame</h1>
|
| 34 |
<p class="hero-sub">Self-Evolving Game Generation through Agentic Verification and Progressive Development</p>
|
|
@@ -39,6 +40,20 @@
|
|
| 39 |
<a class="btn" href="#" aria-disabled="true">Code <small>soon</small></a>
|
| 40 |
<a class="btn" href="#" aria-disabled="true">Dataset <small>soon</small></a>
|
| 41 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
</div>
|
| 43 |
</header>
|
| 44 |
|
|
|
|
| 28 |
<div class="hero-bg" aria-hidden="true">
|
| 29 |
<video id="hero-video" muted loop playsinline preload="auto" data-src="hero_evo.mp4" data-poster="hero_evo.webp"></video>
|
| 30 |
</div>
|
| 31 |
+
<div class="hero-inner has-play">
|
| 32 |
+
<div class="hero-text">
|
| 33 |
<p class="eyebrow">Agentic verification · Progressive development</p>
|
| 34 |
<h1>EvoGame</h1>
|
| 35 |
<p class="hero-sub">Self-Evolving Game Generation through Agentic Verification and Progressive Development</p>
|
|
|
|
| 40 |
<a class="btn" href="#" aria-disabled="true">Code <small>soon</small></a>
|
| 41 |
<a class="btn" href="#" aria-disabled="true">Dataset <small>soon</small></a>
|
| 42 |
</div>
|
| 43 |
+
</div>
|
| 44 |
+
<section class="play-card" id="play" aria-labelledby="play-title" hidden>
|
| 45 |
+
<div class="play-top">
|
| 46 |
+
<span class="play-tag">Playable</span>
|
| 47 |
+
<b id="play-title"></b>
|
| 48 |
+
<span class="play-who" id="play-who"></span>
|
| 49 |
+
</div>
|
| 50 |
+
<div class="play-screen" id="play-screen"></div>
|
| 51 |
+
<div class="play-ctl">
|
| 52 |
+
<div class="seg" id="play-rounds" role="group" aria-label="Which build to play"></div>
|
| 53 |
+
<span class="play-keys" id="play-keys"></span>
|
| 54 |
+
<button class="btn btn-sm play-fs" id="play-fs" type="button" hidden>⛶ Fullscreen</button>
|
| 55 |
+
</div>
|
| 56 |
+
</section>
|
| 57 |
</div>
|
| 58 |
</header>
|
| 59 |
|
media/play/engine/godot.audio.position.worklet.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************/
|
| 2 |
+
/* godot.audio.position.worklet.js */
|
| 3 |
+
/**************************************************************************/
|
| 4 |
+
/* This file is part of: */
|
| 5 |
+
/* GODOT ENGINE */
|
| 6 |
+
/* https://godotengine.org */
|
| 7 |
+
/**************************************************************************/
|
| 8 |
+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
| 9 |
+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
| 10 |
+
/* */
|
| 11 |
+
/* Permission is hereby granted, free of charge, to any person obtaining */
|
| 12 |
+
/* a copy of this software and associated documentation files (the */
|
| 13 |
+
/* "Software"), to deal in the Software without restriction, including */
|
| 14 |
+
/* without limitation the rights to use, copy, modify, merge, publish, */
|
| 15 |
+
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
| 16 |
+
/* permit persons to whom the Software is furnished to do so, subject to */
|
| 17 |
+
/* the following conditions: */
|
| 18 |
+
/* */
|
| 19 |
+
/* The above copyright notice and this permission notice shall be */
|
| 20 |
+
/* included in all copies or substantial portions of the Software. */
|
| 21 |
+
/* */
|
| 22 |
+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
| 23 |
+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
| 24 |
+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
| 25 |
+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
| 26 |
+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
| 27 |
+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
| 28 |
+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
| 29 |
+
/**************************************************************************/
|
| 30 |
+
|
| 31 |
+
class GodotPositionReportingProcessor extends AudioWorkletProcessor {
|
| 32 |
+
static get parameterDescriptors() {
|
| 33 |
+
return [
|
| 34 |
+
{
|
| 35 |
+
name: 'reset',
|
| 36 |
+
defaultValue: 0,
|
| 37 |
+
minValue: 0,
|
| 38 |
+
maxValue: 1,
|
| 39 |
+
automationRate: 'k-rate',
|
| 40 |
+
},
|
| 41 |
+
];
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
constructor(...args) {
|
| 45 |
+
super(...args);
|
| 46 |
+
this.position = 0;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
process(inputs, _outputs, parameters) {
|
| 50 |
+
if (parameters['reset'][0] > 0) {
|
| 51 |
+
this.position = 0;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
if (inputs.length > 0) {
|
| 55 |
+
const input = inputs[0];
|
| 56 |
+
if (input.length > 0) {
|
| 57 |
+
this.position += input[0].length;
|
| 58 |
+
this.port.postMessage({ type: 'position', data: this.position });
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
return true;
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
registerProcessor('godot-position-reporting-processor', GodotPositionReportingProcessor);
|
media/play/engine/godot.audio.worklet.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**************************************************************************/
|
| 2 |
+
/* audio.worklet.js */
|
| 3 |
+
/**************************************************************************/
|
| 4 |
+
/* This file is part of: */
|
| 5 |
+
/* GODOT ENGINE */
|
| 6 |
+
/* https://godotengine.org */
|
| 7 |
+
/**************************************************************************/
|
| 8 |
+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
| 9 |
+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
| 10 |
+
/* */
|
| 11 |
+
/* Permission is hereby granted, free of charge, to any person obtaining */
|
| 12 |
+
/* a copy of this software and associated documentation files (the */
|
| 13 |
+
/* "Software"), to deal in the Software without restriction, including */
|
| 14 |
+
/* without limitation the rights to use, copy, modify, merge, publish, */
|
| 15 |
+
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
| 16 |
+
/* permit persons to whom the Software is furnished to do so, subject to */
|
| 17 |
+
/* the following conditions: */
|
| 18 |
+
/* */
|
| 19 |
+
/* The above copyright notice and this permission notice shall be */
|
| 20 |
+
/* included in all copies or substantial portions of the Software. */
|
| 21 |
+
/* */
|
| 22 |
+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
| 23 |
+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
| 24 |
+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
| 25 |
+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
| 26 |
+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
| 27 |
+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
| 28 |
+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
| 29 |
+
/**************************************************************************/
|
| 30 |
+
|
| 31 |
+
class RingBuffer {
|
| 32 |
+
constructor(p_buffer, p_state, p_threads) {
|
| 33 |
+
this.buffer = p_buffer;
|
| 34 |
+
this.avail = p_state;
|
| 35 |
+
this.threads = p_threads;
|
| 36 |
+
this.rpos = 0;
|
| 37 |
+
this.wpos = 0;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
data_left() {
|
| 41 |
+
return this.threads ? Atomics.load(this.avail, 0) : this.avail;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
space_left() {
|
| 45 |
+
return this.buffer.length - this.data_left();
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
read(output) {
|
| 49 |
+
const size = this.buffer.length;
|
| 50 |
+
let from = 0;
|
| 51 |
+
let to_write = output.length;
|
| 52 |
+
if (this.rpos + to_write > size) {
|
| 53 |
+
const high = size - this.rpos;
|
| 54 |
+
output.set(this.buffer.subarray(this.rpos, size));
|
| 55 |
+
from = high;
|
| 56 |
+
to_write -= high;
|
| 57 |
+
this.rpos = 0;
|
| 58 |
+
}
|
| 59 |
+
if (to_write) {
|
| 60 |
+
output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);
|
| 61 |
+
}
|
| 62 |
+
this.rpos += to_write;
|
| 63 |
+
if (this.threads) {
|
| 64 |
+
Atomics.add(this.avail, 0, -output.length);
|
| 65 |
+
Atomics.notify(this.avail, 0);
|
| 66 |
+
} else {
|
| 67 |
+
this.avail -= output.length;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
write(p_buffer) {
|
| 72 |
+
const to_write = p_buffer.length;
|
| 73 |
+
const mw = this.buffer.length - this.wpos;
|
| 74 |
+
if (mw >= to_write) {
|
| 75 |
+
this.buffer.set(p_buffer, this.wpos);
|
| 76 |
+
this.wpos += to_write;
|
| 77 |
+
if (mw === to_write) {
|
| 78 |
+
this.wpos = 0;
|
| 79 |
+
}
|
| 80 |
+
} else {
|
| 81 |
+
const high = p_buffer.subarray(0, mw);
|
| 82 |
+
const low = p_buffer.subarray(mw);
|
| 83 |
+
this.buffer.set(high, this.wpos);
|
| 84 |
+
this.buffer.set(low);
|
| 85 |
+
this.wpos = low.length;
|
| 86 |
+
}
|
| 87 |
+
if (this.threads) {
|
| 88 |
+
Atomics.add(this.avail, 0, to_write);
|
| 89 |
+
Atomics.notify(this.avail, 0);
|
| 90 |
+
} else {
|
| 91 |
+
this.avail += to_write;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
class GodotProcessor extends AudioWorkletProcessor {
|
| 97 |
+
constructor() {
|
| 98 |
+
super();
|
| 99 |
+
this.threads = false;
|
| 100 |
+
this.running = true;
|
| 101 |
+
this.lock = null;
|
| 102 |
+
this.notifier = null;
|
| 103 |
+
this.output = null;
|
| 104 |
+
this.output_buffer = new Float32Array();
|
| 105 |
+
this.input = null;
|
| 106 |
+
this.input_buffer = new Float32Array();
|
| 107 |
+
this.port.onmessage = (event) => {
|
| 108 |
+
const cmd = event.data['cmd'];
|
| 109 |
+
const data = event.data['data'];
|
| 110 |
+
this.parse_message(cmd, data);
|
| 111 |
+
};
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
process_notify() {
|
| 115 |
+
if (this.notifier) {
|
| 116 |
+
Atomics.add(this.notifier, 0, 1);
|
| 117 |
+
Atomics.notify(this.notifier, 0);
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
parse_message(p_cmd, p_data) {
|
| 122 |
+
if (p_cmd === 'start' && p_data) {
|
| 123 |
+
const state = p_data[0];
|
| 124 |
+
let idx = 0;
|
| 125 |
+
this.threads = true;
|
| 126 |
+
this.lock = state.subarray(idx, ++idx);
|
| 127 |
+
this.notifier = state.subarray(idx, ++idx);
|
| 128 |
+
const avail_in = state.subarray(idx, ++idx);
|
| 129 |
+
const avail_out = state.subarray(idx, ++idx);
|
| 130 |
+
this.input = new RingBuffer(p_data[1], avail_in, true);
|
| 131 |
+
this.output = new RingBuffer(p_data[2], avail_out, true);
|
| 132 |
+
} else if (p_cmd === 'stop') {
|
| 133 |
+
this.running = false;
|
| 134 |
+
this.output = null;
|
| 135 |
+
this.input = null;
|
| 136 |
+
this.lock = null;
|
| 137 |
+
this.notifier = null;
|
| 138 |
+
} else if (p_cmd === 'start_nothreads') {
|
| 139 |
+
this.output = new RingBuffer(p_data[0], p_data[0].length, false);
|
| 140 |
+
} else if (p_cmd === 'chunk') {
|
| 141 |
+
this.output.write(p_data);
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
static array_has_data(arr) {
|
| 146 |
+
return arr.length && arr[0].length && arr[0][0].length;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
process(inputs, outputs, parameters) {
|
| 150 |
+
if (!this.running) {
|
| 151 |
+
return false; // Stop processing.
|
| 152 |
+
}
|
| 153 |
+
if (this.output === null) {
|
| 154 |
+
return true; // Not ready yet, keep processing.
|
| 155 |
+
}
|
| 156 |
+
const process_input = GodotProcessor.array_has_data(inputs);
|
| 157 |
+
if (process_input) {
|
| 158 |
+
const input = inputs[0];
|
| 159 |
+
const chunk = input[0].length * input.length;
|
| 160 |
+
if (this.input_buffer.length !== chunk) {
|
| 161 |
+
this.input_buffer = new Float32Array(chunk);
|
| 162 |
+
}
|
| 163 |
+
if (!this.threads) {
|
| 164 |
+
GodotProcessor.write_input(this.input_buffer, input);
|
| 165 |
+
this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer });
|
| 166 |
+
} else if (this.input.space_left() >= chunk) {
|
| 167 |
+
GodotProcessor.write_input(this.input_buffer, input);
|
| 168 |
+
this.input.write(this.input_buffer);
|
| 169 |
+
} else {
|
| 170 |
+
// this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
const process_output = GodotProcessor.array_has_data(outputs);
|
| 174 |
+
if (process_output) {
|
| 175 |
+
const output = outputs[0];
|
| 176 |
+
const chunk = output[0].length * output.length;
|
| 177 |
+
if (this.output_buffer.length !== chunk) {
|
| 178 |
+
this.output_buffer = new Float32Array(chunk);
|
| 179 |
+
}
|
| 180 |
+
if (this.output.data_left() >= chunk) {
|
| 181 |
+
this.output.read(this.output_buffer);
|
| 182 |
+
GodotProcessor.write_output(output, this.output_buffer);
|
| 183 |
+
if (!this.threads) {
|
| 184 |
+
this.port.postMessage({ 'cmd': 'read', 'data': chunk });
|
| 185 |
+
}
|
| 186 |
+
} else {
|
| 187 |
+
// this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
this.process_notify();
|
| 191 |
+
return true;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
static write_output(dest, source) {
|
| 195 |
+
const channels = dest.length;
|
| 196 |
+
for (let ch = 0; ch < channels; ch++) {
|
| 197 |
+
for (let sample = 0; sample < dest[ch].length; sample++) {
|
| 198 |
+
dest[ch][sample] = source[sample * channels + ch];
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
static write_input(dest, source) {
|
| 204 |
+
const channels = source.length;
|
| 205 |
+
for (let ch = 0; ch < channels; ch++) {
|
| 206 |
+
for (let sample = 0; sample < source[ch].length; sample++) {
|
| 207 |
+
dest[sample * channels + ch] = source[ch][sample];
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
registerProcessor('godot-processor', GodotProcessor);
|
media/play/engine/godot.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
media/play/engine/godot.wasm.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1fc8bd3b0e1369cda3641b553a99db0a069be6b664483f48f568498acd2f0fb6
|
| 3 |
+
size 9379523
|
media/play/godot_gpt/platformer-echo-climb/r0/index.pck
ADDED
|
Binary file (74.8 kB). View file
|
|
|
media/play/godot_gpt/platformer-echo-climb/r30/index.pck
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3932ef16d7e8a32d485659aa46e26ebe0f08c44bd8ebc77f9114ce87ee11faef
|
| 3 |
+
size 25377968
|
play.html
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>EvoGame player</title>
|
| 7 |
+
<!--
|
| 8 |
+
One shared Godot 4.6.2 web engine for every exported game.
|
| 9 |
+
play.html?engine=<base url of godot.js/.wasm.gz>&pck=<game pck url>&pck_size=<bytes>&wasm_gz=<bytes>
|
| 10 |
+
The engine's .wasm is stored gzipped; fetch() for it is answered from the .gz and inflated here.
|
| 11 |
+
-->
|
| 12 |
+
<style>
|
| 13 |
+
html, body { margin: 0; height: 100%; background: #05060a; overflow: hidden; }
|
| 14 |
+
#canvas { display: block; width: 100%; height: 100%; outline: none; }
|
| 15 |
+
#boot { position: absolute; inset: 0; display: grid; place-items: center; color: #b4bccd; font: 500 13px/1.5 ui-monospace, "JetBrains Mono", Menlo, monospace; text-align: center; padding: 16px; }
|
| 16 |
+
#boot[hidden] { display: none; }
|
| 17 |
+
#bar { width: min(320px, 70vw); height: 4px; margin: 10px auto 0; border-radius: 2px; background: #1c2130; overflow: hidden; }
|
| 18 |
+
#bar i { display: block; height: 100%; width: 0; background: #c6f432; transition: width .15s; }
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<canvas id="canvas" tabindex="0">Your browser does not support the canvas element.</canvas>
|
| 23 |
+
<div id="boot"><div><div id="msg">Loading engine…</div><div id="bar"><i></i></div></div></div>
|
| 24 |
+
<script>
|
| 25 |
+
(() => {
|
| 26 |
+
const q = new URLSearchParams(location.search);
|
| 27 |
+
const engineBase = q.get("engine");
|
| 28 |
+
const pck = q.get("pck");
|
| 29 |
+
const pckSize = +q.get("pck_size") || 0;
|
| 30 |
+
const wasmGz = +q.get("wasm_gz") || 0;
|
| 31 |
+
const msg = document.getElementById("msg"), bar = document.querySelector("#bar i");
|
| 32 |
+
const mb = (n) => (n / 1048576).toFixed(1);
|
| 33 |
+
const show = (text, frac) => { msg.textContent = text; if (frac != null) bar.style.width = `${Math.min(100, frac * 100)}%`; };
|
| 34 |
+
const fail = (e) => { console.error(e); show(`Could not start the game: ${e && e.message ? e.message : e}`); };
|
| 35 |
+
|
| 36 |
+
// Answer the engine's request for godot.wasm with the gzipped copy, inflated in the browser.
|
| 37 |
+
const realFetch = window.fetch.bind(window);
|
| 38 |
+
window.fetch = async (input, init) => {
|
| 39 |
+
const url = typeof input === "string" ? input : input.url;
|
| 40 |
+
if (!/\.wasm(\?|$)/.test(url)) return realFetch(input, init);
|
| 41 |
+
const r = await realFetch(url.replace(/\.wasm(\?|$)/, ".wasm.gz$1"), init);
|
| 42 |
+
if (!r.ok) return realFetch(input, init);
|
| 43 |
+
const reader = r.body.getReader(), chunks = [];
|
| 44 |
+
let got = 0;
|
| 45 |
+
for (;;) {
|
| 46 |
+
const { done, value } = await reader.read();
|
| 47 |
+
if (done) break;
|
| 48 |
+
chunks.push(value);
|
| 49 |
+
got += value.length;
|
| 50 |
+
if (wasmGz) show(`Loading engine ${mb(got)} / ${mb(wasmGz)} MB`, (got / wasmGz) * 0.5);
|
| 51 |
+
}
|
| 52 |
+
let blob = new Blob(chunks);
|
| 53 |
+
const head = new Uint8Array(await blob.slice(0, 2).arrayBuffer());
|
| 54 |
+
if (head[0] === 0x1f && head[1] === 0x8b) { // not already inflated by the server
|
| 55 |
+
show("Unpacking engine…", 0.5);
|
| 56 |
+
blob = await new Response(blob.stream().pipeThrough(new DecompressionStream("gzip"))).blob();
|
| 57 |
+
}
|
| 58 |
+
return new Response(blob, { headers: { "Content-Type": "application/wasm", "Content-Length": String(blob.size) } });
|
| 59 |
+
};
|
| 60 |
+
|
| 61 |
+
if (!engineBase || !pck) return fail("missing engine or pck");
|
| 62 |
+
const script = document.createElement("script");
|
| 63 |
+
script.src = `${engineBase}.js`;
|
| 64 |
+
script.onerror = () => fail("engine script did not load");
|
| 65 |
+
script.onload = () => {
|
| 66 |
+
const missing = Engine.getMissingFeatures({ threads: false });
|
| 67 |
+
if (missing.length) return fail(`this browser lacks ${missing.join(", ")}`);
|
| 68 |
+
const engine = new Engine({
|
| 69 |
+
executable: engineBase,
|
| 70 |
+
mainPack: pck,
|
| 71 |
+
args: [],
|
| 72 |
+
canvasResizePolicy: 2,
|
| 73 |
+
focusCanvas: true,
|
| 74 |
+
experimentalVK: false,
|
| 75 |
+
gdextensionLibs: [],
|
| 76 |
+
ensureCrossOriginIsolationHeaders: false,
|
| 77 |
+
fileSizes: { [pck]: pckSize },
|
| 78 |
+
});
|
| 79 |
+
engine.startGame({
|
| 80 |
+
onProgress: (cur, total) => {
|
| 81 |
+
if (total > 0) show(`Loading game ${mb(cur)} / ${mb(total)} MB`, 0.5 + (cur / total) * 0.5);
|
| 82 |
+
},
|
| 83 |
+
}).then(() => {
|
| 84 |
+
document.getElementById("boot").hidden = true;
|
| 85 |
+
document.getElementById("canvas").focus();
|
| 86 |
+
parent.postMessage({ evoPlay: "started" }, "*");
|
| 87 |
+
}, fail);
|
| 88 |
+
};
|
| 89 |
+
document.head.append(script);
|
| 90 |
+
addEventListener("pointerdown", () => document.getElementById("canvas").focus());
|
| 91 |
+
})();
|
| 92 |
+
</script>
|
| 93 |
+
</body>
|
| 94 |
+
</html>
|
static/css/v1.css
CHANGED
|
@@ -81,3 +81,39 @@
|
|
| 81 |
.modal.simple #m-twin { grid-column: 1; }
|
| 82 |
.modal.simple .m-prompt, .modal.simple .m-h, .modal.simple #m-reqhint, .modal.simple #m-reqs, .modal.simple #m-evo { display: none; }
|
| 83 |
@media (max-width: 760px) { .modal.simple .modal-info { grid-template-columns: 1fr; } .modal.simple .m-score { grid-column: 1; grid-row: auto; } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
.modal.simple #m-twin { grid-column: 1; }
|
| 82 |
.modal.simple .m-prompt, .modal.simple .m-h, .modal.simple #m-reqhint, .modal.simple #m-reqs, .modal.simple #m-evo { display: none; }
|
| 83 |
@media (max-width: 760px) { .modal.simple .modal-info { grid-template-columns: 1fr; } .modal.simple .m-score { grid-column: 1; grid-row: auto; } }
|
| 84 |
+
|
| 85 |
+
/* ---------- playable hero ---------- */
|
| 86 |
+
.hero-inner.has-play { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: clamp(28px, 4vw, 56px); align-items: center; max-width: 1320px; width: 100%; text-align: left; }
|
| 87 |
+
.has-play .hero-text { min-width: 0; }
|
| 88 |
+
.has-play h1 { font-size: clamp(56px, 8vw, 112px); }
|
| 89 |
+
.has-play .hero-sub { margin-inline: 0; }
|
| 90 |
+
.has-play .hero-cta { justify-content: flex-start; }
|
| 91 |
+
.play-card { background: rgba(10, 12, 18, .88); border: 1px solid var(--hover-border); border-radius: 18px; padding: 12px; box-shadow: 0 30px 80px rgba(0,0,0,.55); backdrop-filter: blur(10px); display: grid; gap: 10px; min-width: 0; }
|
| 92 |
+
.play-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 2px 4px 0; }
|
| 93 |
+
.play-top b { font: 700 17px var(--display); }
|
| 94 |
+
.play-tag { font: 700 10px var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); background: var(--accent); padding: 3px 7px; border-radius: 5px; }
|
| 95 |
+
.play-who { color: var(--muted); font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
|
| 96 |
+
.play-screen { position: relative; aspect-ratio: 16 / 9; max-width: 100%; border-radius: 10px; overflow: hidden; background: var(--media-bg); }
|
| 97 |
+
.play-screen iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
|
| 98 |
+
.play-poster { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; cursor: pointer; background: var(--media-bg); display: grid; place-items: center; color: var(--text); }
|
| 99 |
+
.play-poster img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(.55); transition: filter .2s, transform .4s; }
|
| 100 |
+
.play-poster:hover img, .play-poster:focus-visible img { filter: brightness(.7); transform: scale(1.02); }
|
| 101 |
+
.play-poster:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
| 102 |
+
.play-go { position: relative; display: grid; justify-items: center; gap: 10px; }
|
| 103 |
+
.play-go i { width: 76px; height: 76px; border-radius: 50%; background: var(--accent); display: grid; place-items: center; box-shadow: 0 0 0 10px rgba(198,244,50,.18); transition: transform .2s; }
|
| 104 |
+
.play-go i::before { content: ""; margin-left: 6px; border-left: 22px solid var(--accent-ink); border-top: 14px solid transparent; border-bottom: 14px solid transparent; }
|
| 105 |
+
.play-poster:hover .play-go i { transform: scale(1.06); }
|
| 106 |
+
.play-go b { font: 700 18px var(--display); text-shadow: 0 2px 12px rgba(0,0,0,.7); }
|
| 107 |
+
.play-go span { font: 500 12px var(--mono); color: var(--text-2); text-shadow: 0 1px 8px rgba(0,0,0,.8); }
|
| 108 |
+
.play-ctl { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
| 109 |
+
.play-ctl .seg button { display: inline-flex; gap: 8px; align-items: baseline; }
|
| 110 |
+
.play-ctl .seg button b { font: 700 12px var(--mono); color: var(--muted); }
|
| 111 |
+
.play-ctl .seg button.on b { color: var(--accent); }
|
| 112 |
+
.play-keys { font: 500 12px var(--mono); color: var(--muted); }
|
| 113 |
+
.play-fs { margin-left: auto; }
|
| 114 |
+
@media (max-width: 1020px) {
|
| 115 |
+
.hero-inner.has-play { grid-template-columns: 1fr; text-align: center; max-width: 820px; }
|
| 116 |
+
.has-play .hero-sub { margin-inline: auto; }
|
| 117 |
+
.has-play .hero-cta { justify-content: center; }
|
| 118 |
+
.play-card { text-align: left; }
|
| 119 |
+
}
|
static/data/play.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"engine": {
|
| 3 |
+
"base": "play/engine/godot",
|
| 4 |
+
"wasm": 37695054,
|
| 5 |
+
"wasm_gz": 9379523
|
| 6 |
+
},
|
| 7 |
+
"games": [
|
| 8 |
+
{
|
| 9 |
+
"arm": "godot_gpt",
|
| 10 |
+
"task": "platformer-echo-climb",
|
| 11 |
+
"title": "Echo Climb",
|
| 12 |
+
"model": "GPT-5.5",
|
| 13 |
+
"controls": "A / D move \u00b7 Space jump \u00b7 R restart",
|
| 14 |
+
"pair": "pairs/godot_gpt/platformer-echo-climb",
|
| 15 |
+
"scores": {
|
| 16 |
+
"r0": 0.4154,
|
| 17 |
+
"r30": 0.8908
|
| 18 |
+
},
|
| 19 |
+
"builds": {
|
| 20 |
+
"r0": {
|
| 21 |
+
"pck": "play/godot_gpt/platformer-echo-climb/r0/index.pck",
|
| 22 |
+
"size": 74800
|
| 23 |
+
},
|
| 24 |
+
"r30": {
|
| 25 |
+
"pck": "play/godot_gpt/platformer-echo-climb/r30/index.pck",
|
| 26 |
+
"size": 25377968
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
]
|
| 31 |
+
}
|
static/js/v1.js
CHANGED
|
@@ -12,6 +12,56 @@ Evo.boot(({ games, arms }) => {
|
|
| 12 |
const hv = $("#hero-video");
|
| 13 |
Evo.mountHeroVideo(hv, { src: hv.dataset.src, poster: hv.dataset.poster });
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
// ---------- headline: one row per generator ----------
|
| 16 |
$("#headline").replaceChildren(...arms.map((a) => {
|
| 17 |
const p0 = mean(a.games.map((g) => g.reward)), p1 = mean(a.games.map((g) => g.repaired.reward));
|
|
|
|
| 12 |
const hv = $("#hero-video");
|
| 13 |
Evo.mountHeroVideo(hv, { src: hv.dataset.src, poster: hv.dataset.poster });
|
| 14 |
|
| 15 |
+
// ---------- playable hero: the real round-0 and round-30 builds, in the browser ----------
|
| 16 |
+
(async function playable() {
|
| 17 |
+
const cfg = window.EVO_PLAY || await fetch(Evo.data("play.json")).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
| 18 |
+
const game = cfg?.games?.[0];
|
| 19 |
+
if (!game) return;
|
| 20 |
+
const screen = $("#play-screen"), rounds = $("#play-rounds"), fsBtn = $("#play-fs");
|
| 21 |
+
const ROUNDS = [["r0", "Round 0"], ["r30", "Round 30"]];
|
| 22 |
+
const label = (r) => ROUNDS.find(([k]) => k === r)[1];
|
| 23 |
+
let sel = "r30", running = false;
|
| 24 |
+
$("#play-title").textContent = game.title;
|
| 25 |
+
$("#play-who").replaceChildren(dot(game.model), `${game.model} · Godot 4`);
|
| 26 |
+
$("#play-keys").textContent = game.controls;
|
| 27 |
+
|
| 28 |
+
function idle() {
|
| 29 |
+
running = false;
|
| 30 |
+
fsBtn.hidden = true;
|
| 31 |
+
const mbs = Math.round((cfg.engine.wasm_gz + game.builds[sel].size) / 1048576);
|
| 32 |
+
const poster = h("button", { class: "play-poster", type: "button", "aria-label": `Play ${game.title}, ${label(sel)}` },
|
| 33 |
+
h("img", { src: Evo.media(`${game.pair}/${sel === "r0" ? "p0" : "final"}.webp`), alt: "" }),
|
| 34 |
+
h("span", { class: "play-go" }, h("i"), h("b", {}, `Play ${label(sel)} in your browser`), h("span", {}, `${mbs} MB download · keyboard`)));
|
| 35 |
+
poster.addEventListener("click", start);
|
| 36 |
+
screen.replaceChildren(poster);
|
| 37 |
+
}
|
| 38 |
+
function start() {
|
| 39 |
+
running = true;
|
| 40 |
+
Evo.pauseAll();
|
| 41 |
+
$("#hero-video").pause();
|
| 42 |
+
const b = game.builds[sel];
|
| 43 |
+
const src = "play.html?" + new URLSearchParams({
|
| 44 |
+
engine: Evo.media(cfg.engine.base), wasm_gz: cfg.engine.wasm_gz, pck: Evo.media(b.pck), pck_size: b.size,
|
| 45 |
+
});
|
| 46 |
+
const frame = h("iframe", { src, title: `${game.title}, ${label(sel)}`, allow: "autoplay; fullscreen; gamepad" });
|
| 47 |
+
frame.addEventListener("load", () => frame.contentWindow?.focus());
|
| 48 |
+
screen.replaceChildren(frame);
|
| 49 |
+
fsBtn.hidden = false;
|
| 50 |
+
}
|
| 51 |
+
rounds.replaceChildren(...ROUNDS.map(([r, t]) => h("button", {
|
| 52 |
+
type: "button", class: r === sel ? "on" : "", "data-r": r,
|
| 53 |
+
onclick: () => {
|
| 54 |
+
if (sel === r) return;
|
| 55 |
+
sel = r;
|
| 56 |
+
rounds.querySelectorAll("button").forEach((x) => x.classList.toggle("on", x.dataset.r === r));
|
| 57 |
+
running ? start() : idle();
|
| 58 |
+
},
|
| 59 |
+
}, t, h("b", {}, s100(game.scores[r])))));
|
| 60 |
+
fsBtn.addEventListener("click", () => screen.querySelector("iframe")?.requestFullscreen?.());
|
| 61 |
+
idle();
|
| 62 |
+
$("#play").hidden = false;
|
| 63 |
+
})();
|
| 64 |
+
|
| 65 |
// ---------- headline: one row per generator ----------
|
| 66 |
$("#headline").replaceChildren(...arms.map((a) => {
|
| 67 |
const p0 = mean(a.games.map((g) => g.reward)), p1 = mean(a.games.map((g) => g.repaired.reward));
|