diff --git a/assets/berd-cursor-inverse.png b/assets/berd-cursor-inverse.png
new file mode 100644
index 0000000..907878f
Binary files /dev/null and b/assets/berd-cursor-inverse.png differ
diff --git a/assets/berd-cursor-inverse.svg b/assets/berd-cursor-inverse.svg
new file mode 100644
index 0000000..f397dcc
--- /dev/null
+++ b/assets/berd-cursor-inverse.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/source/arroost/components/dom.js b/source/arroost/components/dom.js
index 01e346e..470784b 100644
--- a/source/arroost/components/dom.js
+++ b/source/arroost/components/dom.js
@@ -153,7 +153,7 @@ export class Dom extends Component {
this.use(() => {
const outOfView = this.outOfView.get()
- container.style.display = outOfView ? "block" : "block"
+ container.style.display = outOfView ? "hidden" : "block"
}, [this.outOfView, this.forceInView])
const element = this.getElement()
diff --git a/source/arroost/components/infinite.js b/source/arroost/components/infinite.js
index b78a961..bb328f0 100644
--- a/source/arroost/components/infinite.js
+++ b/source/arroost/components/infinite.js
@@ -1,4 +1,4 @@
-import { BLACK, RED, scale } from "../../../libraries/habitat-import.js"
+import { BLACK, RED, rotate, scale } from "../../../libraries/habitat-import.js"
import { shared } from "../../main.js"
import { Dragging } from "../machines/input.js"
import { Component } from "./component.js"
@@ -8,12 +8,20 @@ import { Transform } from "./transform.js"
import { Movement } from "./movement.js"
import { Style } from "./style.js"
import { c, getCell, getTemplate, iterateCells, iterateWires, t } from "../../nogan/nogan.js"
-import { CHILD_SCALE, PARENT_SCALE, ZOOMING_IN_THRESHOLD, ZOOM_IN_THRESHOLD } from "../unit.js"
+import {
+ CHILD_SCALE,
+ FULL,
+ PARENT_SCALE,
+ ZOOMING_IN_THRESHOLD,
+ ZOOM_IN_THRESHOLD,
+} from "../unit.js"
import { ArrowOfCreation } from "../entities/arrows/creation.js"
-import { CELL_CONSTRUCTORS, WIRE_CONSTRUCTOR } from "./tunnel.js"
+import { CELL_CONSTRUCTORS, Tunnel, WIRE_CONSTRUCTOR } from "./tunnel.js"
import { Entity } from "../entities/entity.js"
import { EASE, lerp } from "../../../libraries/lerp.js"
import { Ellipse } from "../entities/shapes/ellipse.js"
+import { ArrowOfConnection } from "../entities/arrows/connection.js"
+import { ArrowOfDestruction } from "../entities/arrows/destruction.js"
export function ilerp(x, a, b) {
return (x - a) / (b - a)
@@ -68,7 +76,7 @@ export class Infinite extends Component {
// const parentBlur = lerp([0, 20], t, EASE.easeInOutExpo)
this.dom?.style.opacity.set(childOpacity)
- this.parent?.style.opacity.set(parentOpacity)
+ // this.parent?.style.opacity.set(parentOpacity)
// this.dom?.style.blur.set(childBlur)
// this.parent?.style.blur.set(parentBlur)
@@ -89,7 +97,9 @@ export class Infinite extends Component {
this.use(() => {
switch (this.state.get()) {
case "zooming-in": {
- this.appendContentsForLevel(shared.level)
+ if (!this.dom?.input.entity?.tunnel) throw new Error("Missing tunnel")
+ const tunnel = this.dom.input.entity.tunnel
+ this.appendContentsForLevel(tunnel.id)
break
}
case "none": {
@@ -112,14 +122,21 @@ export class Infinite extends Component {
}
/**
- * @param {number} level
+ * @param {number} newLevel
*/
- appendContentsForLevel(level) {
+ appendContentsForLevel(newLevel) {
const cellEntities = []
const wireEntities = []
+ const oldLevelCell = getCell(shared.nogan, shared.level)
+ const newLevelCell = getCell(shared.nogan, newLevel)
+
+ if (!oldLevelCell || !newLevelCell) {
+ throw new Error("Missing level cell - this shouldn't happen")
+ }
+
for (const cell of iterateCells(shared.nogan)) {
- if (cell.parent !== level) continue
+ if (cell.parent !== newLevel) continue
const entity = CELL_CONSTRUCTORS[cell.type]({
id: cell.id,
position: cell.position,
@@ -139,7 +156,7 @@ export class Infinite extends Component {
for (const wire of iterateWires(shared.nogan)) {
const cells = wire.cells.map((id) => getCell(shared.nogan, id))
- if (!cells.every((cell) => cell?.parent === level)) continue
+ if (!cells.every((cell) => cell?.parent === newLevel)) continue
const entity = WIRE_CONSTRUCTOR({
id: wire.id,
@@ -156,7 +173,40 @@ export class Infinite extends Component {
}
}
+ if (cellEntities.length === 0) {
+ const distance = FULL * 2
+ const angle = Math.random() * Math.PI * 2
+
+ /** @type {[number, number][]} */
+ const positions = [rotate([distance, 0], angle), rotate([-distance, 0], angle)]
+
+ const entity1 = new ArrowOfCreation({
+ position: positions[0],
+ preview: true,
+ level: newLevel,
+ })
+ cellEntities.push(entity1)
+ this.previews.add(entity1)
+
+ const entity2 = new ArrowOfConnection({
+ position: positions[1],
+ preview: true,
+ level: newLevel,
+ })
+ cellEntities.push(entity2)
+ this.previews.add(entity2)
+
+ // const entity3 = new ArrowOfDestruction({
+ // position: positions[2],
+ // preview: true,
+ // level: newLevel,
+ // })
+ // cellEntities.push(entity3)
+ // this.previews.add(entity3)
+ }
+
const background = new Ellipse()
+ this.background = background
background.dom.style.fill.set(BLACK.toString())
const parent = this.parent
diff --git a/source/arroost/components/style.js b/source/arroost/components/style.js
index 1c4c14b..ddd412b 100644
--- a/source/arroost/components/style.js
+++ b/source/arroost/components/style.js
@@ -1,9 +1,10 @@
-import { glue, GREY, use } from "../../../libraries/habitat-import.js"
+import { BLACK, glue, GREY, use } from "../../../libraries/habitat-import.js"
import { shared } from "../../main.js"
+import { theme } from "../../theme.js"
import { Component } from "./component.js"
import { Dom } from "./dom.js"
-export const Style = class extends Component {
+export class Style extends Component {
static highestZIndex = 0
static lowestZIndex = 0
@@ -57,9 +58,15 @@ export const Style = class extends Component {
/** @type {Signal} */
opacity = this.use(null)
- static SHADOW = "0px 4px 8px rgba(0, 0, 0, 0.25), 0px 0px 4px rgba(0, 0, 0, 0.15)"
- static SHADOW_FILTER =
- "drop-shadow(0px 4px 8px rgba(0, 0, 0, 0.25)) drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.15))"
+ static SHADOW_COLOUR = theme.get() === "dark" ? "0, 0, 0" : "0, 0, 20"
+ static SHADOW_OPACITY = theme.get() === "dark" ? 0.25 : 0.03
+
+ static SHADOW = `0px 4px 8px rgba(${Style.SHADOW_COLOUR}, ${
+ Style.SHADOW_OPACITY
+ }), 0px 0px 4px rgba(${Style.SHADOW_COLOUR}, ${Style.SHADOW_OPACITY * 0.6})`
+ static SHADOW_FILTER = `drop-shadow(0px 4px 8px rgba(${Style.SHADOW_COLOUR}, ${
+ Style.SHADOW_OPACITY
+ })) drop-shadow(0px 0px 4px rgba(${Style.SHADOW_COLOUR}, ${Style.SHADOW_OPACITY * 0.6}))`
/**
* @param {SVGElement} element
@@ -153,3 +160,5 @@ export const Style = class extends Component {
this.zIndex.set(zIndex)
}
}
+
+window["Style"] = Style
diff --git a/source/arroost/components/tunnel.js b/source/arroost/components/tunnel.js
index 9808823..15cd935 100644
--- a/source/arroost/components/tunnel.js
+++ b/source/arroost/components/tunnel.js
@@ -56,6 +56,26 @@ export class Tunnel extends Component {
*/
static inViewInfiniteTunnels = new Set()
+ static purgeOtherLevelInfiniteTunnels() {
+ for (const [key, tunnel] of Tunnel.tunnels) {
+ const cell = getCell(shared.nogan, tunnel.id)
+ if (!cell) throw new Error(`Tunnel: Can't find cell ${tunnel.id}`)
+ if (cell.parent !== shared.level) {
+ tunnel.entity.dispose()
+ Tunnel.tunnels.delete(key)
+ }
+ }
+
+ for (const tunnel of Tunnel.inViewInfiniteTunnels) {
+ const cell = getCell(shared.nogan, tunnel.id)
+ if (!cell) throw new Error(`Tunnel: Can't find cell ${tunnel.id}`)
+ if (cell.parent !== shared.level) {
+ tunnel.entity.dispose()
+ Tunnel.inViewInfiniteTunnels.delete(tunnel)
+ }
+ }
+ }
+
/**
* @param {Operation[]} operations
*/
@@ -209,6 +229,11 @@ export class Tunnel extends Component {
}
if (this.type === "cell") {
+ const cell = getCell(shared.nogan, this.id)
+ if (!cell) throw new Error(`Tunnel: Can't find cell ${this.id}`)
+ if (cell.type === "timing" || cell.type === "colour") {
+ return
+ }
this.useCell({
dom: this.entity.dom,
carry: this.entity.carry,
@@ -229,10 +254,11 @@ export class Tunnel extends Component {
/**
* @param {CellId | WireId} id
* @param {Map} store
+ * @param {boolean} quiet
*/
- static delete(id, store = Tunnel.tunnels) {
+ static delete(id, store = Tunnel.tunnels, quiet = false) {
const tunnel = Tunnel.get(id, store)
- if (!tunnel) throw new Error(`Tunnel: Can't find tunnel ${id} to delete`)
+ if (!tunnel && !quiet) throw new Error(`Tunnel: Can't find tunnel ${id} to delete`)
store.delete(id)
if (store === Tunnel.tunnels) {
@@ -253,7 +279,7 @@ export class Tunnel extends Component {
dispose() {
super.dispose()
const store = this.isPreview ? Tunnel.tunnelPreviews : Tunnel.tunnels
- Tunnel.delete(this.id, store)
+ Tunnel.delete(this.id, store, true)
}
/**
@@ -272,16 +298,11 @@ export class Tunnel extends Component {
const cell = getCell(shared.nogan, this.id)
if (!cell) throw new Error(`Tunnel: Can't find cell ${this.id}`)
if (equals(cell.position, position)) return
+ if (!equals(velocity, [0, 0])) return
Tunnel.apply(() => {
return moveCell(shared.nogan, {
id: this.id,
position,
- propogate: equals(velocity, [0, 0]),
- filter: (id) => {
- const cell = getCell(shared.nogan, id)
- if (!cell) throw new Error(`Tunnel: Can't find cell ${id}`)
- return cell.type === "slot"
- },
})
})
})
@@ -457,8 +478,9 @@ export const CELL_CONSTRUCTORS = {
// Must be called only after all relevant cells have been created
export const WIRE_CONSTRUCTOR = ({ id, colour, timing, source, target, preview }) => {
- const targetTunnel = Tunnel.get(target, Tunnel.tunnelPreviews)
- const sourceTunnel = Tunnel.get(source, Tunnel.tunnelPreviews)
+ const store = preview ? Tunnel.tunnelPreviews : Tunnel.tunnels
+ const targetTunnel = Tunnel.get(target, store)
+ const sourceTunnel = Tunnel.get(source, store)
const targetEntity = targetTunnel?.entity
const sourceEntity = sourceTunnel?.entity
@@ -477,3 +499,5 @@ export const WIRE_CONSTRUCTOR = ({ id, colour, timing, source, target, preview }
preview,
})
}
+
+window["Tunnel"] = Tunnel
diff --git a/source/arroost/cursor.js b/source/arroost/cursor.js
index a07eb2f..e1007d2 100644
--- a/source/arroost/cursor.js
+++ b/source/arroost/cursor.js
@@ -1,5 +1,6 @@
import { use } from "../../libraries/habitat-import.js"
import { shared } from "../main.js"
+import { theme } from "../theme.js"
export function useCursor() {
use(() => {
@@ -12,7 +13,10 @@ export function useCursor() {
}
case "berd": {
- const path = "/assets/berd-cursor.svg"
+ const path =
+ theme.get() === "dark"
+ ? "/assets/berd-cursor.svg"
+ : "/assets/berd-cursor-inverse.svg"
document.body.style.cursor = `url(${path}) 0 20, auto`
return
}
diff --git a/source/arroost/entities/arrows/colour.js b/source/arroost/entities/arrows/colour.js
index ce3fbdd..a98727c 100644
--- a/source/arroost/entities/arrows/colour.js
+++ b/source/arroost/entities/arrows/colour.js
@@ -8,7 +8,7 @@ import {
WHITE,
equals,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import {
createCell,
createWire,
diff --git a/source/arroost/entities/arrows/connection.js b/source/arroost/entities/arrows/connection.js
index 1760cbe..40a6a60 100644
--- a/source/arroost/entities/arrows/connection.js
+++ b/source/arroost/entities/arrows/connection.js
@@ -23,7 +23,8 @@ export class ArrowOfConnection extends Entity {
pulling = this.use(false)
constructor({
- id = createCell(shared.nogan, { parent: shared.level, type: "connection" }).id,
+ level = shared.level,
+ id = createCell(shared.nogan, { parent: level, type: "connection" }).id,
position = t([0, 0]),
preview = false,
}) {
diff --git a/source/arroost/entities/arrows/creation.js b/source/arroost/entities/arrows/creation.js
index 089f158..f67cb87 100644
--- a/source/arroost/entities/arrows/creation.js
+++ b/source/arroost/entities/arrows/creation.js
@@ -25,7 +25,8 @@ export class ArrowOfCreation extends Entity {
pulling = this.use(false)
constructor({
- id = createCell(shared.nogan, { parent: shared.level, type: "creation" }).id,
+ level = shared.level,
+ id = createCell(shared.nogan, { parent: level, type: "creation" }).id,
position = t([0, 0]),
preview = false,
}) {
diff --git a/source/arroost/entities/arrows/destruction.js b/source/arroost/entities/arrows/destruction.js
index 52e80db..3a7e248 100644
--- a/source/arroost/entities/arrows/destruction.js
+++ b/source/arroost/entities/arrows/destruction.js
@@ -34,7 +34,8 @@ export class ArrowOfDestruction extends Entity {
pulling = this.use(false)
constructor({
- id = createCell(shared.nogan, { parent: shared.level, type: "destruction" }).id,
+ level = shared.level,
+ id = createCell(shared.nogan, { parent: level, type: "destruction" }).id,
position = t([0, 0]),
preview = false,
}) {
diff --git a/source/arroost/entities/arrows/dummy.js b/source/arroost/entities/arrows/dummy.js
index d5e6df8..05a3165 100644
--- a/source/arroost/entities/arrows/dummy.js
+++ b/source/arroost/entities/arrows/dummy.js
@@ -8,7 +8,7 @@ import {
WHITE,
equals,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import {
createCell,
fireCell,
diff --git a/source/arroost/entities/arrows/noise.js b/source/arroost/entities/arrows/noise.js
index 11a2ff5..e6d0696 100644
--- a/source/arroost/entities/arrows/noise.js
+++ b/source/arroost/entities/arrows/noise.js
@@ -5,7 +5,7 @@ import { Input } from "../../components/input.js"
import { setCellStyles } from "./shared.js"
import { ArrowOfRecording } from "./recording.js"
import { RectangleHtml } from "../shapes/rectangle-html.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import { GREY } from "../../../../libraries/habitat-import.js"
import { FIFTH, FULL, HALF, TENTH, THIRD } from "../../unit.js"
diff --git a/source/arroost/entities/arrows/reality.js b/source/arroost/entities/arrows/reality.js
index 3d24b74..3be7617 100644
--- a/source/arroost/entities/arrows/reality.js
+++ b/source/arroost/entities/arrows/reality.js
@@ -8,7 +8,7 @@ import {
WHITE,
equals,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import {
createCell,
fireCell,
diff --git a/source/arroost/entities/arrows/recording.js b/source/arroost/entities/arrows/recording.js
index 212158e..37c69ab 100644
--- a/source/arroost/entities/arrows/recording.js
+++ b/source/arroost/entities/arrows/recording.js
@@ -10,7 +10,7 @@ import {
equals,
subtract,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, MIDDLE_C, shared } from "../../../main.js"
+import { MIDDLE_C, shared } from "../../../main.js"
import {
SharedResource,
createCell,
diff --git a/source/arroost/entities/arrows/shared.js b/source/arroost/entities/arrows/shared.js
index 3849ee9..af4da2b 100644
--- a/source/arroost/entities/arrows/shared.js
+++ b/source/arroost/entities/arrows/shared.js
@@ -1,5 +1,5 @@
import { WHITE, BLACK, GREY, SILVER } from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER } from "../../../main.js"
+import { GREY_SILVER, theme } from "../../../theme.js"
import { Dom } from "../../components/dom.js"
import { Infinite } from "../../components/infinite.js"
import { Input } from "../../components/input.js"
@@ -41,7 +41,7 @@ export const setCellStyles = ({
})
back.style.shadow.set(true)
- back.style.stroke.set("rgba(0, 0, 0, 0.25)")
+ back.style.stroke.set(theme.get() === "dark" ? "rgba(0, 0, 0, 0.25)" : "rgba(0, 0, 20, 0.02)")
back.style.strokeWidth.set(2)
// back.style.pointerEvents.set(infinite?.isPreview ? "none" : "all")
diff --git a/source/arroost/entities/arrows/slot.js b/source/arroost/entities/arrows/slot.js
index 47f003a..567944d 100644
--- a/source/arroost/entities/arrows/slot.js
+++ b/source/arroost/entities/arrows/slot.js
@@ -8,7 +8,7 @@ import {
WHITE,
equals,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import {
createCell,
fireCell,
@@ -70,8 +70,8 @@ export class ArrowOfSlot extends Entity {
this.dom.append(this.front.dom)
// Style elements
- this.back.dom.transform.scale.set([2 / 3, 2 / 3])
- this.front.dom.transform.scale.set([1 / 3, 1 / 3])
+ this.back.dom.transform.scale.set([1, 1])
+ this.front.dom.transform.scale.set([1 / 2, 1 / 2])
setCellStyles({
back: this.back.dom,
front: this.front.dom,
diff --git a/source/arroost/entities/arrows/time.js b/source/arroost/entities/arrows/time.js
index cdeb667..03d2950 100644
--- a/source/arroost/entities/arrows/time.js
+++ b/source/arroost/entities/arrows/time.js
@@ -9,7 +9,7 @@ import {
subtract,
} from "../../../../libraries/habitat-import.js"
import { shared } from "../../../main.js"
-import { createWire } from "../../../nogan/nogan.js"
+import { createWire, getCell, getWire } from "../../../nogan/nogan.js"
import { Dom } from "../../components/dom.js"
import { Tunnel } from "../../components/tunnel.js"
import { Entity } from "../entity.js"
@@ -47,6 +47,9 @@ export class ArrowOfTime extends Entity {
this.target = target
this.isPreview = preview
+ let timingId = undefined
+ let colourId = undefined
+
// Setup tunnel
if (id === undefined) {
const { wire, operations } = createWire(shared.nogan, {
@@ -62,12 +65,21 @@ export class ArrowOfTime extends Entity {
Tunnel.apply(() => operations)
} else {
this.tunnel = this.attach(new Tunnel(id, { entity: this, forcePreview: this.isPreview }))
+ const wireWire = getWire(shared.nogan, id)
+ for (const cellId of wireWire.cells) {
+ const cell = getCell(shared.nogan, cellId)
+ if (cell?.type === "timing") {
+ timingId = cellId
+ } else if (cell?.type === "colour") {
+ colourId = cellId
+ }
+ }
}
// Render elements
this.line = this.attach(new Line())
- this.flaps = this.attach(new ArrowOfTiming({ wire: this.tunnel.id }))
- this.colour = this.attach(new ArrowOfColour({ wire: this.tunnel.id }))
+ this.flaps = this.attach(new ArrowOfTiming({ wire: this.tunnel.id, id: timingId }))
+ this.colour = this.attach(new ArrowOfColour({ wire: this.tunnel.id, id: colourId }))
this.dom.append(this.line.dom)
if (!this.isPreview) {
diff --git a/source/arroost/entities/arrows/timing.js b/source/arroost/entities/arrows/timing.js
index 40d69e2..e1da4d5 100644
--- a/source/arroost/entities/arrows/timing.js
+++ b/source/arroost/entities/arrows/timing.js
@@ -8,7 +8,7 @@ import {
WHITE,
equals,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import {
createCell,
createWire,
diff --git a/source/arroost/entities/arrows/typing.js b/source/arroost/entities/arrows/typing.js
index b2e08dc..4dd4c35 100644
--- a/source/arroost/entities/arrows/typing.js
+++ b/source/arroost/entities/arrows/typing.js
@@ -8,7 +8,7 @@ import {
WHITE,
equals,
} from "../../../../libraries/habitat-import.js"
-import { GREY_SILVER, shared } from "../../../main.js"
+import { shared } from "../../../main.js"
import {
createCell,
fireCell,
diff --git a/source/arroost/entities/scene.js b/source/arroost/entities/scene.js
index 0be2ef7..ec47c87 100644
--- a/source/arroost/entities/scene.js
+++ b/source/arroost/entities/scene.js
@@ -1,4 +1,4 @@
-import { GREY_SILVER, shared } from "../../main.js"
+import { shared } from "../../main.js"
import { Entity } from "./entity.js"
import { Dom } from "../components/dom.js"
import {
@@ -9,6 +9,7 @@ import {
distanceBetween,
equals,
fireEvent,
+ rotate,
scale,
subtract,
use,
@@ -20,18 +21,30 @@ import { replenishUnlocks } from "./unlock.js"
import { Title } from "./title.js"
import { TextHtml } from "./shapes/text-html.js"
import { Transform } from "../components/transform.js"
-import { Tunnel } from "../components/tunnel.js"
+import { CELL_CONSTRUCTORS, Tunnel, WIRE_CONSTRUCTOR } from "../components/tunnel.js"
import {
CHILD_SCALE,
+ FULL,
PARENT_SCALE,
ZOOMING_IN_THRESHOLD,
ZOOMING_OUT_THRESHOLD,
ZOOM_IN_THRESHOLD,
} from "../unit.js"
-import { c, getCell, t } from "../../nogan/nogan.js"
+import {
+ c,
+ createCell,
+ getCell,
+ getTemplate,
+ iterateCells,
+ iterateWires,
+ t,
+} from "../../nogan/nogan.js"
import { Infinite } from "../components/infinite.js"
import { checkUnderPointer, triggerSomethingHasMoved } from "../machines/hover.js"
import { Marker } from "./debug/marker.js"
+import { ArrowOfTiming } from "./arrows/timing.js"
+import { ArrowOfColour } from "./arrows/colour.js"
+import { GREY_SILVER } from "../../theme.js"
const ZOOM_FRICTION = 0.75
@@ -306,6 +319,9 @@ export class Scene extends Entity {
let distanceFromScreenCenter = Infinity
let closestTunnel = null
for (const tunnel of Tunnel.inViewInfiniteTunnels.values()) {
+ const cell = getCell(shared.nogan, tunnel.id)
+ if (!cell) throw new Error("No cell found for tunnel")
+ if (cell.parent !== shared.level) continue
const { transform } = tunnel.entity.dom
const position = transform.position.get()
const distance = distanceBetween(position, this.bounds.get().center)
@@ -373,9 +389,15 @@ export class Scene extends Entity {
replaceLayer(tunnel) {
for (const layerName of this.sceneLayerNames) {
const layer = this.layer[layerName]
- // layer.dispose()
+ layer.dispose()
}
+ shared.level = tunnel.id
+
+ Tunnel.purgeOtherLevelInfiniteTunnels()
+ this.recreateSceneLayers()
+ this.rebuildWorldFromNogan()
+
const { center } = this.bounds.get()
const targetPosition = tunnel.entity.dom.transform.absolutePosition.get()
const position = scale(subtract(center, targetPosition), PARENT_SCALE)
@@ -391,9 +413,52 @@ export class Scene extends Entity {
}
replaceLayerBackwards() {
+ for (const layerName of this.sceneLayerNames) {
+ const layer = this.layer[layerName]
+ layer.dispose()
+ }
+
+ const oldLevelCell = getCell(shared.nogan, shared.level)
+ if (!oldLevelCell) throw new Error("Missing level cell - this shouldn't happen")
+ shared.level = oldLevelCell.parent
+
+ let cellToComeOutOf = oldLevelCell.id
+ // If we're at the top level, just find any cell on the top level
+ if (shared.level === 0 && oldLevelCell.id === 0) {
+ // Purge everything, because we're gonna remake it
+ shared.level = NaN
+ Tunnel.purgeOtherLevelInfiniteTunnels()
+ shared.level = 0
+
+ const newCell = createCell(shared.nogan, {
+ type: "creation",
+ })
+
+ cellToComeOutOf = newCell.id
+
+ for (const cell of iterateCells(shared.nogan)) {
+ if (cell.id === 0) continue
+ if (cell.id === newCell.id) continue
+ if (cell.parent === 0) {
+ cell.parent = newCell.id
+ break
+ }
+ }
+
+ // createCell(shared.nogan, {
+ // type: "connection",
+ // position: rotate([FULL * 2, 0], Math.random() * Math.PI * 2),
+ // })
+ }
+
+ Tunnel.purgeOtherLevelInfiniteTunnels()
+ this.recreateSceneLayers()
+ this.rebuildWorldFromNogan()
+
const { center } = this.bounds.get()
- const cell = getCell(shared.nogan, 1)
+ const cell = getCell(shared.nogan, cellToComeOutOf)
if (!cell) {
+ console.warn("Couldn't find cell to come out of")
return
// throw new Error("Couldn't find cell to come out of")
}
@@ -405,6 +470,59 @@ export class Scene extends Entity {
this.setCameraCenter(position)
this.dealWithInfinites()
}
+
+ rebuildWorldFromNogan() {
+ const wireEntities = []
+
+ /** @type { (Entity & {dom: Dom; infinite?: Infinite})[] } */
+ const cellEntities = []
+
+ for (const cell of iterateCells(shared.nogan)) {
+ if (cell.parent !== shared.level) continue
+ const entity = CELL_CONSTRUCTORS[cell.type]({
+ id: cell.id,
+ position: cell.position,
+ template: getTemplate(cell),
+ })
+
+ if (!entity) continue
+ cellEntities.push(entity)
+ }
+
+ for (const wire of iterateWires(shared.nogan)) {
+ const cells = wire.cells.map((id) => getCell(shared.nogan, id))
+ if (!cells.every((cell) => cell?.parent === shared.level)) continue
+
+ const entity = WIRE_CONSTRUCTOR({
+ id: wire.id,
+ colour: wire.colour,
+ timing: wire.timing,
+ source: wire.source,
+ target: wire.target,
+ preview: false,
+ })
+ wireEntities.push(entity)
+ }
+
+ for (const entity of wireEntities) {
+ this.layer.wire.append(entity.dom)
+ }
+
+ for (const entity of cellEntities) {
+ if (entity instanceof ArrowOfTiming) {
+ this.layer.timing.append(entity.dom)
+ } else if (entity instanceof ArrowOfColour) {
+ this.layer.timing.append(entity.dom)
+ } else {
+ this.layer.cell.append(entity.dom)
+ if (entity.infinite) {
+ entity.infinite.background?.dom.style.sendToBack()
+ // entity.infinite.dom?.style.bringToFront()
+ entity.dom.style.bringToFront()
+ }
+ }
+ }
+ }
}
addEventListener("keydown", (e) => {
diff --git a/source/arroost/entities/tool.js b/source/arroost/entities/tool.js
index c580775..ff50564 100644
--- a/source/arroost/entities/tool.js
+++ b/source/arroost/entities/tool.js
@@ -14,6 +14,7 @@ export function selectTool(type) {
let nearestDistance = Infinity
for (const cell of iterateCells(shared.nogan)) {
if (cell.type !== type) continue
+ if (cell.parent !== shared.level) continue
const distance = distanceBetween(cell.position, pointer)
if (distance < nearestDistance) {
nearestDistance = distance
diff --git a/source/clock.js b/source/clock.js
index 7a7314c..fec7bb0 100644
--- a/source/clock.js
+++ b/source/clock.js
@@ -1,10 +1,10 @@
import { BLACK, GREY, VOID } from "../libraries/habitat-import.js"
import { Tunnel } from "./arroost/components/tunnel.js"
-import { GREY_BLACK, shared } from "./main.js"
+import { shared } from "./main.js"
import { getAdvanced } from "./nogan/nogan.js"
class Clock {
- bpm = 120
+ bpm = 160
/** @type {"buildup" | "aftermath"} */
phase = "buildup"
@@ -13,6 +13,10 @@ class Clock {
queue = []
count = 0
+ setBpm(bpm) {
+ this.bpm = bpm
+ Tone.Transport.bpm.value = bpm
+ }
start() {
const metronome = new Tone.PluckSynth().toDestination()
diff --git a/source/main.js b/source/main.js
index f3e3972..6833d07 100644
--- a/source/main.js
+++ b/source/main.js
@@ -22,6 +22,7 @@ import { getZoomer } from "./arroost/input/zoomer.js"
import { clock } from "./clock.js"
import { registerImporters } from "./arroost/machines/import.js"
import { useCursor } from "./arroost/cursor.js"
+import { useTheme } from "./theme.js"
//======//
// Tone //
@@ -35,8 +36,7 @@ window["print"] = print
window["dir"] = console.dir.bind(console)
registerMethods()
-export const GREY_SILVER = new Colour(83, 101, 147)
-export const GREY_BLACK = new Colour(31, 39, 54)
+useTheme()
//==============//
// Setup Engine //
diff --git a/source/nogan/nogan.js b/source/nogan/nogan.js
index 75ebbe3..5084e57 100644
--- a/source/nogan/nogan.js
+++ b/source/nogan/nogan.js
@@ -1334,12 +1334,13 @@ export const fullFireCell = (
/**
* Clone a nogan, ending all fires of cells whose parents are firing.
* @param {Nogan} nogan
+ * @param {GetPeakMemo | undefined} memo
* @returns {{
* projection: Nogan,
* operations: Operation[],
* }}
*/
-export const getProjection = (nogan) => {
+export const getProjection = (nogan, memo = undefined) => {
const projection = getClone(nogan)
const operations = []
@@ -1356,7 +1357,7 @@ export const getProjection = (nogan) => {
if (!cell.fire.blue && !cell.fire.red && !cell.fire.green) continue
// Don't unfire the cell if its parent isn't firing
- if (!isRoot(parent) && !isPeakFiring(nogan, { id: parent })) continue
+ if (!isRoot(parent) && !isPeakFiring(nogan, { id: parent, memo })) continue
cell.fire = createFire()
const unfiredOperation = c({ type: "unfired", id: cell.id })
@@ -1465,6 +1466,24 @@ export const getPeak = (
const from = timing === 1 ? past : future
// First, let's try to look in the known future/past
+ // But wait! only if my parent is firing
+ // const cell = getCell(nogan, id)
+ // if (!cell) throw new Error(`Couldn't find cell ${id} to get peak`)
+ // const { parent } = cell
+ // if (
+ // !isRoot(parent) &&
+ // !isPeakFiring(nogan, {
+ // id: parent,
+ // past,
+ // future,
+ // memo,
+ // timing: getFlippedTiming(timing),
+ // })
+ // ) {
+ // return createPeak({ colour })
+ // }
+
+ // Parent is firing, so let's look in the known future/past!
const [next, ...rest] = to
if (next) {
const peak = getPeakNow(next, {
@@ -1479,7 +1498,7 @@ export const getPeak = (
}
// Otherwise, let's prepare to imagine the future/past
- const { projection } = getProjection(nogan)
+ const { projection } = getProjection(nogan, memo)
// But wait!
// Are we stuck in a loop?
@@ -1536,6 +1555,24 @@ const getPeakNow = (nogan, { id, colour, past, future, memo = new GetPeakMemo()
})
if (!inputPeak.result) continue
+
+ // Check if the input's parent is firing
+ const inputCell = getCell(nogan, wire.source)
+ if (!inputCell) throw new Error(`Couldn't find input cell ${wire.source}`)
+ const { parent } = inputCell
+ if (
+ !isRoot(parent) &&
+ !isPeakFiring(nogan, {
+ id: parent,
+ timing: getFlippedTiming(wire.timing),
+ past,
+ future,
+ memo,
+ })
+ ) {
+ continue
+ }
+
peak = getBehavedPeak({
nogan,
source: wire.source,
@@ -1571,12 +1608,16 @@ export const isFiring = (nogan, { id }) => {
* @param {Nogan} nogan
* @param {{
* id: CellId,
+ * timing?: Timing,
+ * past?: Nogan[],
+ * future?: Nogan[],
+ * memo?: GetPeakMemo,
* }} options
* @returns {boolean}
*/
-export const isPeakFiring = (nogan, { id }) => {
+export const isPeakFiring = (nogan, options) => {
for (const colour of PULSE_COLOURS) {
- const peak = getPeak(nogan, { id, colour })
+ const peak = getPeak(nogan, { ...options, colour })
if (peak.result) return true
}
return false
@@ -1639,6 +1680,12 @@ export const refresh = (
let memo = new GetPeakMemo()
for (const id of iterateCellIds(snapshot)) {
if (filter && !filter(id)) continue
+ const cell = getCell(snapshot, id)
+ if (!cell) throw new Error(`Couldn't find cell ${id} to refresh`)
+ // if (!isRoot(cell.parent) && !isPeakFiring(nogan, { id: cell.parent, memo, past, future })) {
+ // continue
+ // }
+
for (const colour of PULSE_COLOURS) {
const peak = getPeak(snapshot, {
id,
@@ -1658,7 +1705,10 @@ export const refresh = (
// If we've changed the nogan, we need to refresh the cache
// (as things might be different now)
- memo = new GetPeakMemo()
+
+ // COMMENT OUT FOR BUGS BUT HUGE PERFORMANCE INCREASE
+ // memo = new GetPeakMemo()
+
operations.push(...firedOperations)
}
}
diff --git a/source/nogan/nogan.test.js b/source/nogan/nogan.test.js
index f5a992a..7235e5c 100644
--- a/source/nogan/nogan.test.js
+++ b/source/nogan/nogan.test.js
@@ -1723,3 +1723,64 @@ describe("memoisation", () => {
assertEquals(operations.length, 3) // Should have fired all three slots
})
})
+
+describe("infinity", () => {
+ it("deals with wires inside cells", () => {
+ const nogan = createNogan()
+ const cell1 = createCell(nogan, { type: "slot" })
+ const cell2 = createCell(nogan, { type: "slot" })
+ createWire(nogan, { source: cell1.id, target: cell2.id, timing: 1 })
+ createWire(nogan, { source: cell2.id, target: cell1.id, timing: 1 })
+
+ const child1 = createCell(nogan, { type: "slot", parent: cell1.id })
+ const child2 = createCell(nogan, { type: "slot", parent: cell1.id })
+ createWire(nogan, { source: child1.id, target: child2.id, timing: 1 })
+
+ const advanced0 = getAdvanced(nogan).advanced
+ const cell1after0 = getCell(advanced0, cell1.id)
+ const cell2after0 = getCell(advanced0, cell2.id)
+ const child1after0 = getCell(advanced0, child1.id)
+ const child2after0 = getCell(advanced0, child2.id)
+
+ assertEquals(cell1after0?.fire.blue, null)
+ assertEquals(cell2after0?.fire.blue, null)
+ assertEquals(child1after0?.fire.blue, null)
+ assertEquals(child2after0?.fire.blue, null)
+
+ fireCell(nogan, { id: child1.id })
+
+ assertEquals(cell1.fire.blue, null)
+ assertEquals(cell2.fire.blue, null)
+ assertEquals(child1.fire.blue, { type: "raw" })
+ assertEquals(child2.fire.blue, null)
+
+ const advanced1 = getAdvanced(nogan).advanced
+ const cell1after1 = getCell(advanced1, cell1.id)
+ const cell2after1 = getCell(advanced1, cell2.id)
+ const child1after1 = getCell(advanced1, child1.id)
+ const child2after1 = getCell(advanced1, child2.id)
+
+ assertEquals(cell1after1?.fire.blue, null)
+ assertEquals(cell2after1?.fire.blue, null)
+ assertEquals(child1after1?.fire.blue, { type: "raw" })
+ assertEquals(child2after1?.fire.blue, null)
+
+ fireCell(nogan, { id: cell1.id })
+
+ assertEquals(cell1.fire.blue, { type: "raw" })
+ assertEquals(cell2.fire.blue, null)
+ assertEquals(child1.fire.blue, { type: "raw" })
+ assertEquals(child2.fire.blue, null)
+
+ const advanced2 = getAdvanced(nogan).advanced
+ const cell1after2 = getCell(advanced2, cell1.id)
+ const cell2after2 = getCell(advanced2, cell2.id)
+ const child1after2 = getCell(advanced2, child1.id)
+ const child2after2 = getCell(advanced2, child2.id)
+
+ assertEquals(cell1after2?.fire.blue, null)
+ assertEquals(cell2after2?.fire.blue, { type: "raw" })
+ assertEquals(child1after2?.fire.blue, null)
+ assertEquals(child2after2?.fire.blue, { type: "raw" })
+ })
+})
diff --git a/source/theme.js b/source/theme.js
new file mode 100644
index 0000000..0731db1
--- /dev/null
+++ b/source/theme.js
@@ -0,0 +1,72 @@
+import {
+ add,
+ BLACK,
+ Colour,
+ COLOURS,
+ GREY,
+ SILVER,
+ use,
+ WHITE,
+} from "../libraries/habitat-import.js"
+
+export const GREY_SILVER = new Colour(83, 101, 147)
+export const GREY_BLACK = new Colour(31, 39, 54)
+
+/** @type {Signal<"light" | "dark">} */
+export const theme = use("light")
+
+const BASE = {
+ WHITE,
+ BLACK,
+ GREY,
+ GREY_SILVER,
+ GREY_BLACK,
+ SILVER,
+}
+
+window["BASE"] = BASE
+
+const DARK = {
+ WHITE: [...WHITE],
+ BLACK: [...BLACK],
+ GREY: [...GREY],
+ GREY_SILVER: [...GREY_SILVER],
+ GREY_BLACK: [...GREY_BLACK],
+}
+
+const LIGHT = {
+ WHITE: [...BLACK],
+ BLACK: [...new Colour(255, 255, 255)],
+ GREY: [...SILVER],
+ GREY_SILVER: [...SILVER],
+ GREY_BLACK: [...SILVER],
+}
+
+export function useTheme() {
+ addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ theme.set(theme.get() === "light" ? "dark" : "light")
+ }
+ })
+
+ use(() => {
+ switch (theme.get()) {
+ case "light": {
+ for (const key in LIGHT) {
+ BASE[key][0] = LIGHT[key][0]
+ BASE[key][1] = LIGHT[key][1]
+ BASE[key][2] = LIGHT[key][2]
+ }
+ break
+ }
+ case "dark": {
+ for (const key in DARK) {
+ BASE[key][0] = DARK[key][0]
+ BASE[key][1] = DARK[key][1]
+ BASE[key][2] = DARK[key][2]
+ }
+ break
+ }
+ }
+ }, [theme])
+}