From 9ee3428578812c124de94b6c6a236b0a54998ec3 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Thu, 8 Jan 2026 09:10:37 -0600 Subject: [PATCH] update --- clay2.cm | 35 +- examples/chess/grid.cm | 16 +- particle2d.cm | 5 - playdate.cm | 57 ---- prosperon.cm | 1 - scene.cm | 650 ------------------------------------- tests/animation.ce | 2 +- tests/camera_colorspace.ce | 2 +- 8 files changed, 30 insertions(+), 738 deletions(-) delete mode 100644 particle2d.cm delete mode 100644 scene.cm diff --git a/clay2.cm b/clay2.cm index dac43103..172721db 100644 --- a/clay2.cm +++ b/clay2.cm @@ -37,6 +37,17 @@ var base_config = { behave: 0 } +function normalize_color(c, fallback) { + fallback = fallback || {r:1, g:1, b:1, a:1} + if (!c) return {r:fallback.r, g:fallback.g, b:fallback.b, a:fallback.a} + return { + r: c.r != null ? c.r : fallback.r, + g: c.g != null ? c.g : fallback.g, + b: c.b != null ? c.b : fallback.b, + a: c.a != null ? c.a : fallback.a + } +} + function normalize_spacing(s) { if (is_number(s)) return {l:s, r:s, t:s, b:s} if (is_array(s)) { @@ -58,7 +69,6 @@ var tree_stack = [] clay.layout = function(fn, size) { lay_ctx.reset() var root_id = lay_ctx.item() - if (is_array(size)) size = {width: size[0], height: size[1]} lay_ctx.set_size(root_id, size) lay_ctx.set_contain(root_id, layout.contain.row) @@ -90,19 +100,6 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s var abs_y = root_height - (rect.y + rect.height) var abs_x = rect.x - // Our absolute position including parent offsets logic from original clay? - // Actually layout engine gives rects relative to root usually? - // No, layout engine gives RELATIVE logic but `get_rect` usually returns computed layout relative to parent or absolute? - // Let's assume `get_rect` returns relative to parent. - // Wait, `clay.cm` assumed `rect.x` was absolute? - // "Calculate relative position for the group: rel_x = abs_x - parent_abs_x". - // This implies `rect.x` is absolute. - - // Let's verify standard behavior. If `layout` returns absolute coords, we don't need to accumulate parent_abs_x for position, - // BUT we do need it if `clay` was doing local group offsets. - // Original `clay2.cm`: `var rel_x = abs_x - parent_abs_x`. This implies `rect` is absolute. - // So `abs_x` IS `rect.x`. - // IMPORTANT: The offset in config is applied VISUALLY. var vis_x = abs_x + node.config.offset.x var vis_y = abs_y + node.config.offset.y @@ -219,7 +216,15 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s // --- Item Creation Helpers --- function process_configs(configs) { - return meme(base_config, ...configs) + var cfg = meme(base_config, ...configs) + + cfg.color = normalize_color(cfg.color, base_config.color) + if (cfg.background_color) cfg.background_color = normalize_color(cfg.background_color, {r:1,g:1,b:1,a:1}) + + if (!cfg.offset) cfg.offset = {x:0, y:0} + else cfg.offset = {x: cfg.offset.x || 0, y: cfg.offset.y || 0} + + return cfg } function push_node(configs, contain_mode) { diff --git a/examples/chess/grid.cm b/examples/chess/grid.cm index c5880de4..087e6a3d 100644 --- a/examples/chess/grid.cm +++ b/examples/chess/grid.cm @@ -3,9 +3,9 @@ function grid(w, h) { this.height = h; // create a height×width array of empty lists this.cells = new Array(h); - for (let y = 0; y < h; y++) { + for (var y = 0; y < h; y++) { this.cells[y] = new Array(w); - for (let x = 0; x < w; x++) { + for (var x = 0; x < w; x++) { this.cells[y][x] = []; // each cell holds its own list } } @@ -45,10 +45,10 @@ grid.prototype = { // call fn(entity, coord) for every entity in every cell each(fn) { - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { + for (var y = 0; y < this.height; y++) { + for (var x = 0; x < this.width; x++) { def list = this.cells[y][x]; - for (let entity of list) { + for (var entity of list) { fn(entity, entity.coord); } } @@ -57,9 +57,9 @@ grid.prototype = { // printable representation toString() { - let out = `grid [${this.width}×${this.height}]\n`; - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { + var out = `grid [${this.width}×${this.height}]\n`; + for (var y = 0; y < this.height; y++) { + for (var x = 0; x < this.width; x++) { out += this.cells[y][x].length; } if (y != this.height - 1) out += "\n"; diff --git a/particle2d.cm b/particle2d.cm deleted file mode 100644 index a340e2d1..00000000 --- a/particle2d.cm +++ /dev/null @@ -1,5 +0,0 @@ -// particles - -return function() { - -} \ No newline at end of file diff --git a/playdate.cm b/playdate.cm index fad3e11e..b4ce0f62 100644 --- a/playdate.cm +++ b/playdate.cm @@ -432,60 +432,3 @@ PlaydateBackend.prototype.cpu_box_blur = function(src, dst, radius) { dst.setData(dst_data) } -// ======================================================================== -// KEY DIFFERENCES FROM SDL3 BACKEND -// ======================================================================== - -// 1. NO SHADERS -// - shader_pass() degrades gracefully (copy, or CPU fallback) -// - Complex effects (bloom, CRT) either disabled or approximated - -// 2. NATIVE MASKING -// - apply_mask() is BETTER on Playdate (uses setMask() API) -// - SDL3 needs shader or stencil buffer -// - Playdate has it built-in - -// 3. 1-BIT DISPLAY -// - Colors become dither patterns -// - Alpha becomes dither density -// - Blend modes approximate (XOR for additive) - -// 4. CPU RENDERING -// - Some effects run on CPU (blur) -// - Slower but possible -// - Can skip effects if too slow (check frame time) - -// 5. NO COMMAND BUFFER -// - Commands execute immediately -// - No batching at GPU level -// - But batching still happens at Level 2 (same as SDL3) - -// 6. LIMITED SCALING -// - Only nearest-neighbor -// - No bilinear filter -// - Fine for pixel art! - -// ======================================================================== -// WHAT STAYS THE SAME -// ======================================================================== - -// - Scene tree traversal (Level 2) -// - Drawable collection (Level 2) -// - Sorting (Level 2) -// - Batching (Level 2) -// - Node graph structure (Level 2) -// - High-level API (Level 1) - -// Only THIS FILE changes between SDL3 and Playdate. -// Game code is 100% identical. - -// Example: Bullets with emissive lighting -// -// SDL3: Bullets → bloom shader → additive composite (GPU) -// Playdate: Bullets → XOR composite (approximation) -// -// Same high-level code: -// compositor.add_emissive_lighting({tag: 'bullets'}) -// -// Different results, but both correct for their platform. -// That's the point of the abstraction! \ No newline at end of file diff --git a/prosperon.cm b/prosperon.cm index 0386ada3..77a250dd 100644 --- a/prosperon.cm +++ b/prosperon.cm @@ -480,7 +480,6 @@ function pack_model_buffer(material) { return stone(result_blob); } -var imgui = use('imgui') imgui.init(window, device) var rasterize = use('rasterize'); diff --git a/scene.cm b/scene.cm deleted file mode 100644 index f9323276..00000000 --- a/scene.cm +++ /dev/null @@ -1,650 +0,0 @@ -// scene.cm - Retained Scene Graph with Dirty Tracking -// -// Provides retained nodes with: -// - Persistent identity and dirty flags -// - Cached geometry data -// - World transform computation -// - Automatic dirty propagation - -var blob_mod = use('blob') - -// Dirty flag bitmask -var DIRTY = { - NONE: 0, - TRANSFORM: 1, - CONTENT: 2, - CHILDREN: 4, - ALL: 7 -} - -// Base node prototype - all nodes inherit from this -var BaseNode = { - id: null, - type: 'node', - dirty: DIRTY.ALL, - parent: null, - layer: 0, - - // Local properties - pos: null, - opacity: 1, - - // Computed world properties (updated during scene.update) - world_pos: null, - world_tint: null, - world_opacity: 1, - bounds: null, - - // Backend-specific handle (for playdate sprites, etc.) - backend_handle: null, - - mark_dirty: function(flags) { - this.dirty |= flags - }, - - set_pos: function(x, y) { - if (!this.pos) this.pos = {x: 0, y: 0} - if (this.pos.x == x && this.pos.y == y) return this - this.pos.x = x - this.pos.y = y - this.dirty |= DIRTY.TRANSFORM - return this - }, - - set_opacity: function(o) { - if (this.opacity == o) return this - this.opacity = o - this.dirty |= DIRTY.CONTENT - return this - } -} - -// Sprite node prototype -var SpriteNode = { - type: 'sprite', - - // Source properties - image: null, - width: 1, - height: 1, - anchor: null, - color: null, - uv_rect: null, - slice: null, - tile: null, - material: null, - - // Geometry cache - geom_cache: null, - - set_image: function(img) { - if (this.image == img) return this - this.image = img - this.dirty |= DIRTY.CONTENT - if (this.geom_cache) this.geom_cache.texture_key = img - return this - }, - - set_size: function(w, h) { - if (this.width == w && this.height == h) return this - this.width = w - this.height = h - this.dirty |= DIRTY.CONTENT - return this - }, - - set_anchor: function(x, y) { - if (!this.anchor) this.anchor = {x: 0, y: 0} - if (this.anchor.x == x && this.anchor.y == y) return this - this.anchor.x = x - this.anchor.y = y - this.dirty |= DIRTY.TRANSFORM - return this - }, - - set_color: function(r, g, b, a) { - if (!this.color) this.color = {r: 1, g: 1, b: 1, a: 1} - if (this.color.r == r && this.color.g == g && this.color.b == b && this.color.a == a) return this - this.color.r = r - this.color.g = g - this.color.b = b - this.color.a = a - this.dirty |= DIRTY.CONTENT - return this - }, - - rebuild_geometry: function() { - if (this.slice) return this._build_9slice_geom() - if (this.tile) return this._build_tiled_geom() - return this._build_quad_geom() - }, - - _build_quad_geom: function() { - if (!this.geom_cache) { - this.geom_cache = { - verts: null, - indices: null, - vert_count: 0, - index_count: 0, - texture_key: this.image - } - } - - var verts = new blob_mod(128) // 4 verts * 8 floats * 4 bytes - var indices = new blob_mod(12) // 6 indices * 2 bytes - - var ax = this.anchor ? this.anchor.x : 0 - var ay = this.anchor ? this.anchor.y : 0 - var x = this.world_pos.x - this.width * ax - var y = this.world_pos.y - this.height * ay - var w = this.width - var h = this.height - - var c = this.color || {r: 1, g: 1, b: 1, a: 1} - var alpha = c.a * this.world_opacity - - var uv = this.uv_rect || {x: 0, y: 0, width: 1, height: 1} - var u0 = uv.x - var v0 = uv.y - var u1 = uv.x + uv.width - var v1 = uv.y + uv.height - - // v0: bottom-left - verts.wf(x); verts.wf(y) - verts.wf(u0); verts.wf(v1) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(alpha) - - // v1: bottom-right - verts.wf(x + w); verts.wf(y) - verts.wf(u1); verts.wf(v1) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(alpha) - - // v2: top-right - verts.wf(x + w); verts.wf(y + h) - verts.wf(u1); verts.wf(v0) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(alpha) - - // v3: top-left - verts.wf(x); verts.wf(y + h) - verts.wf(u0); verts.wf(v0) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(alpha) - - indices.w16(0); indices.w16(1); indices.w16(2) - indices.w16(0); indices.w16(2); indices.w16(3) - - this.geom_cache.verts = stone(verts) - this.geom_cache.indices = stone(indices) - this.geom_cache.vert_count = 4 - this.geom_cache.index_count = 6 - this.geom_cache.texture_key = this.image - }, - - _build_9slice_geom: function() { - // TODO: Implement 9-slice geometry building - this._build_quad_geom() - }, - - _build_tiled_geom: function() { - // TODO: Implement tiled geometry building - this._build_quad_geom() - } -} - -// Tilemap node prototype -var TilemapNode = { - type: 'tilemap', - - tile_size: null, - tiles: null, - offset: null, - - geom_cache: null, - - set_tile: function(x, y, image) { - if (!this.tiles) this.tiles = [] - if (!this.tiles[x]) this.tiles[x] = [] - if (this.tiles[x][y] == image) return this - this.tiles[x][y] = image - this.dirty |= DIRTY.CONTENT - return this - }, - - rebuild_geometry: function() { - if (!this.geom_cache) { - this.geom_cache = { - verts: null, - indices: null, - vert_count: 0, - index_count: 0, - batches: [] - } - } - - if (!this.tiles) { - this.geom_cache.vert_count = 0 - this.geom_cache.index_count = 0 - this.geom_cache.batches = [] - return - } - - // Count tiles and group by texture - var tile_count = 0 - var texture_map = {} - - var ts = this.tile_size || {x: 1, y: 1} - var off = this.offset || {x: 0, y: 0} - - for (var x = 0; x < this.tiles.length; x++) { - if (!this.tiles[x]) continue - for (var y = 0; y < this.tiles[x].length; y++) { - var t = this.tiles[x][y] - if (!t) continue - if (!texture_map[t]) texture_map[t] = [] - texture_map[t].push({x: x, y: y}) - tile_count++ - } - } - - if (tile_count == 0) { - this.geom_cache.vert_count = 0 - this.geom_cache.index_count = 0 - this.geom_cache.batches = [] - return - } - - var verts = new blob_mod(tile_count * 4 * 32) - var indices = new blob_mod(tile_count * 6 * 2) - - var vert_offset = 0 - var index_offset = 0 - var batches = [] - - for (var tex in texture_map) { - var batch_start = index_offset / 2 - var tiles_list = texture_map[tex] - - for (var i = 0; i < tiles_list.length; i++) { - var tile = tiles_list[i] - var wx = this.world_pos.x + (tile.x + off.x) * ts.x - var wy = this.world_pos.y + (tile.y + off.y) * ts.y - var tw = ts.x - var th = ts.y - - // 4 vertices - verts.wf(wx); verts.wf(wy); verts.wf(0); verts.wf(1) - verts.wf(1); verts.wf(1); verts.wf(1); verts.wf(this.world_opacity) - - verts.wf(wx + tw); verts.wf(wy); verts.wf(1); verts.wf(1) - verts.wf(1); verts.wf(1); verts.wf(1); verts.wf(this.world_opacity) - - verts.wf(wx + tw); verts.wf(wy + th); verts.wf(1); verts.wf(0) - verts.wf(1); verts.wf(1); verts.wf(1); verts.wf(this.world_opacity) - - verts.wf(wx); verts.wf(wy + th); verts.wf(0); verts.wf(0) - verts.wf(1); verts.wf(1); verts.wf(1); verts.wf(this.world_opacity) - - // 6 indices - var base = vert_offset - indices.w16(base); indices.w16(base + 1); indices.w16(base + 2) - indices.w16(base); indices.w16(base + 2); indices.w16(base + 3) - - vert_offset += 4 - index_offset += 12 - } - - batches.push({ - texture: tex, - start_index: batch_start, - index_count: tiles_list.length * 6 - }) - } - - this.geom_cache.verts = stone(verts) - this.geom_cache.indices = stone(indices) - this.geom_cache.vert_count = vert_offset - this.geom_cache.index_count = index_offset / 2 - this.geom_cache.batches = batches - } -} - -// Text node prototype -var TextNode = { - type: 'text', - - text: '', - font: 'fonts/dos', - size: 16, - mode: 'bitmap', - color: null, - anchor: null, - outline_width: 0, - outline_color: null, - - measured: null, - geom_cache: null, - - set_text: function(t) { - if (this.text == t) return this - this.text = t - this.dirty |= DIRTY.CONTENT - return this - }, - - set_font: function(f, s, m) { - if (this.font == f && this.size == s && this.mode == m) return this - this.font = f - if (s != null) this.size = s - if (m != null) this.mode = m - this.dirty |= DIRTY.CONTENT - return this - }, - - rebuild_geometry: function(font_system) { - // Text geometry is built by the backend using font_system - // We just mark that we need rebuild - if (!this.geom_cache) { - this.geom_cache = { - verts: null, - indices: null, - vert_count: 0, - index_count: 0, - font_texture: null - } - } - } -} - -// Group node prototype -var GroupNode = { - type: 'group', - - children: null, - effects: null, - space: '2d', - scissor: null, - - bounds: null, - - add_child: function(node) { - if (!this.children) this.children = [] - node.parent = this - this.children.push(node) - this.dirty |= DIRTY.CHILDREN - return this - }, - - remove_child: function(node) { - if (!this.children) return this - var idx = this.children.indexOf(node) - if (idx >= 0) { - this.children.splice(idx, 1) - node.parent = null - this.dirty |= DIRTY.CHILDREN - } - return this - }, - - clear_children: function() { - if (!this.children) return this - for (var c of this.children) { - c.parent = null - } - this.children = [] - this.dirty |= DIRTY.CHILDREN - return this - } -} - -// Particle node prototype -var ParticleNode = { - type: 'particles', - - image: null, - width: 1, - height: 1, - particles: null, - max_particles: 1000, - - geom_cache: null, - - init: function() { - if (!this.geom_cache) { - this.geom_cache = { - verts: null, - indices: null, - active_count: 0, - capacity: this.max_particles - } - } - - // Pre-allocate index buffer (never changes) - var indices = new blob_mod(this.max_particles * 6 * 2) - for (var i = 0; i < this.max_particles; i++) { - var base = i * 4 - indices.w16(base) - indices.w16(base + 1) - indices.w16(base + 2) - indices.w16(base) - indices.w16(base + 2) - indices.w16(base + 3) - } - this.geom_cache.indices = stone(indices) - return this - }, - - rebuild_geometry: function() { - if (!this.particles || this.particles.length == 0) { - if (this.geom_cache) this.geom_cache.active_count = 0 - return - } - - if (!this.geom_cache) this.init() - - var verts = new blob_mod(this.max_particles * 4 * 32) - var count = 0 - - for (var i = 0; i < this.particles.length && count < this.max_particles; i++) { - var p = this.particles[i] - var hw = this.width * (p.scale || 1) * 0.5 - var hh = this.height * (p.scale || 1) * 0.5 - var px = this.world_pos.x + (p.pos ? p.pos.x : 0) - var py = this.world_pos.y + (p.pos ? p.pos.y : 0) - var c = p.color || {r: 1, g: 1, b: 1, a: 1} - - // v0: bottom-left - verts.wf(px - hw); verts.wf(py - hh); verts.wf(0); verts.wf(1) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(c.a) - - // v1: bottom-right - verts.wf(px + hw); verts.wf(py - hh); verts.wf(1); verts.wf(1) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(c.a) - - // v2: top-right - verts.wf(px + hw); verts.wf(py + hh); verts.wf(1); verts.wf(0) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(c.a) - - // v3: top-left - verts.wf(px - hw); verts.wf(py + hh); verts.wf(0); verts.wf(0) - verts.wf(c.r); verts.wf(c.g); verts.wf(c.b); verts.wf(c.a) - - count++ - } - - this.geom_cache.verts = stone(verts) - this.geom_cache.active_count = count - } -} - -// Scene graph manager -var SceneGraph = { - root: null, - all_nodes: null, - dirty_nodes: null, - _id_counter: 0, - - stats: { - total_nodes: 0, - dirty_this_frame: 0, - geometry_rebuilds: 0 - }, - - _gen_id: function() { - return 'node_' + text(this._id_counter++) - }, - - create: function(type, props) { - props = props || {} - var node = null - - switch (type) { - case 'sprite': - node = meme(SpriteNode) - break - case 'tilemap': - node = meme(TilemapNode) - break - case 'text': - node = meme(TextNode) - break - case 'group': - node = meme(GroupNode) - break - case 'particles': - node = meme(ParticleNode) - break - default: - node = meme(BaseNode) - node.type = type || 'node' - } - - // Apply base node properties - node.id = props.id || this._gen_id() - node.dirty = DIRTY.ALL - node.parent = null - node.layer = props.layer || 0 - node.pos = props.pos || {x: 0, y: 0} - node.opacity = props.opacity != null ? props.opacity : 1 - node.world_pos = {x: 0, y: 0} - node.world_tint = {r: 1, g: 1, b: 1, a: 1} - node.world_opacity = 1 - - // Apply type-specific properties - for (var k in props) { - if (k != 'id' && k != 'layer' && k != 'pos' && k != 'opacity') { - node[k] = props[k] - } - } - - if (!this.all_nodes) this.all_nodes = {} - this.all_nodes[node.id] = node - this.stats.total_nodes++ - - return node - }, - - remove: function(node) { - if (!node) return - if (this.all_nodes && this.all_nodes[node.id]) { - delete this.all_nodes[node.id] - this.stats.total_nodes-- - } - if (node.parent) { - node.parent.remove_child(node) - } - // Recursively remove children - if (node.children) { - for (var c of node.children) { - this.remove(c) - } - } - }, - - // Update world transforms and rebuild dirty geometry - update: function(font_system) { - this.stats.dirty_this_frame = 0 - this.stats.geometry_rebuilds = 0 - this.dirty_nodes = [] - - if (!this.root) return - - // Phase 1: Propagate transforms - this._update_transforms(this.root, {x: 0, y: 0}, {r: 1, g: 1, b: 1, a: 1}, 1) - - // Phase 2: Rebuild geometry for dirty nodes - for (var i = 0; i < this.dirty_nodes.length; i++) { - var node = this.dirty_nodes[i] - if (node.rebuild_geometry) { - if (node.type == 'text') { - node.rebuild_geometry(font_system) - } else { - node.rebuild_geometry() - } - this.stats.geometry_rebuilds++ - } - node.dirty = DIRTY.NONE - } - }, - - _update_transforms: function(node, parent_pos, parent_tint, parent_opacity) { - if (!node) return - - var pos_changed = (node.dirty & DIRTY.TRANSFORM) != 0 - var content_changed = (node.dirty & DIRTY.CONTENT) != 0 - - // Compute world position - var node_pos = node.pos || {x: 0, y: 0} - node.world_pos = {x: parent_pos.x + node_pos.x, y: parent_pos.y + node_pos.y} - - // Compute world tint - var nt = node.color || node.tint || {r: 1, g: 1, b: 1, a: 1} - node.world_tint = { - r: parent_tint.r * nt.r, - g: parent_tint.g * nt.g, - b: parent_tint.b * nt.b, - a: parent_tint.a * nt.a - } - - node.world_opacity = parent_opacity * (node.opacity != null ? node.opacity : 1) - - // Mark for geometry rebuild if needed - if (pos_changed || content_changed) { - this.dirty_nodes.push(node) - this.stats.dirty_this_frame++ - } - - // Recurse children - if (node.children) { - for (var i = 0; i < node.children.length; i++) { - this._update_transforms(node.children[i], node.world_pos, node.world_tint, node.world_opacity) - } - } - }, - - // Find node by id - find: function(id) { - return this.all_nodes ? this.all_nodes[id] : null - }, - - // Clear entire scene - clear: function() { - this.root = null - this.all_nodes = {} - this.dirty_nodes = [] - this._id_counter = 0 - this.stats.total_nodes = 0 - } -} - -// Factory function - returns a new scene graph instance -return function() { - var sg = meme(SceneGraph) - sg.all_nodes = {} - sg.dirty_nodes = [] - sg._id_counter = 0 - sg.stats = { - total_nodes: 0, - dirty_this_frame: 0, - geometry_rebuilds: 0 - } - return sg -} diff --git a/tests/animation.ce b/tests/animation.ce index 078165fd..a9d737bd 100644 --- a/tests/animation.ce +++ b/tests/animation.ce @@ -73,7 +73,7 @@ var fpsTimer=0, fpsCount=0 Anim.minDelay = 1 / 100; // 10 ms, feel free to tune later -let last = os.now(); +var last = os.now(); function loop(){ def now = os.now(); diff --git a/tests/camera_colorspace.ce b/tests/camera_colorspace.ce index 7344816c..204231da 100644 --- a/tests/camera_colorspace.ce +++ b/tests/camera_colorspace.ce @@ -6,7 +6,7 @@ var json = use('json'); var cameras = camera.list(); if (cameras.length == 0) { log.console("No cameras found!"); - $ stop(); + $stop(); } var cam_id = cameras[0];