Research-Stack/0-Core-Formalism/lean/Semantics/Semantics/GoxelGridBus.lean
2026-05-11 22:14:31 -05:00

253 lines
7.7 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
GoxelGridBus.lean - Goxel grid state machines over an internal serial bus
This module makes the goxel workbench idea executable: a goxel field is a
series of finite grid-local state machines, and cells communicate by addressed
packets carried through the existing braid serial transport surface.
-/
import Semantics.BraidSerial
set_option linter.dupNamespace false
namespace Semantics.GoxelGridBus
open Semantics.BraidSerial
open Semantics.FixedPoint
/-- Local finite phase of one goxel cell. -/
inductive GoxelPhase where
| empty
| idle
| active
| settled
| hold
deriving Repr, DecidableEq, BEq
/-- Finite bus commands. -/
inductive GoxelCommand where
| ping
| activate
| settle
| hold
deriving Repr, DecidableEq, BEq
namespace GoxelCommand
def toByte : GoxelCommand → UInt8
| ping => 0
| activate => 1
| settle => 2
| hold => 3
def ofByte (b : UInt8) : GoxelCommand :=
match b.toNat with
| 1 => activate
| 2 => settle
| 3 => hold
| _ => ping
end GoxelCommand
/-- Bounded grid address. Values are byte-sized at the serial boundary. -/
structure GoxelAddr where
row : UInt8
col : UInt8
deriving Repr, DecidableEq, BEq
namespace GoxelAddr
def zero : GoxelAddr := { row := 0, col := 0 }
def seqNum (a : GoxelAddr) : UInt16 :=
UInt16.ofNat (a.row.toNat * 256 + a.col.toNat)
def fromSeqNum (n : UInt16) : GoxelAddr :=
{ row := UInt8.ofNat (n.toNat / 256)
, col := UInt8.ofNat (n.toNat % 256) }
end GoxelAddr
/-- One goxel-local grid state machine. -/
structure GoxelCell where
addr : GoxelAddr
phase : GoxelPhase
scalar : Q16_16
residual : Q16_16
ticks : Nat
deriving Repr, DecidableEq, BEq
namespace GoxelCell
def empty (addr : GoxelAddr) : GoxelCell :=
{ addr := addr
, phase := GoxelPhase.empty
, scalar := Q16_16.zero
, residual := Q16_16.zero
, ticks := 0 }
def idle (addr : GoxelAddr) : GoxelCell :=
{ addr := addr
, phase := GoxelPhase.idle
, scalar := Q16_16.zero
, residual := Q16_16.zero
, ticks := 0 }
end GoxelCell
/-- Addressed packet on the internal bus. -/
structure GoxelBusPacket where
source : GoxelAddr
target : GoxelAddr
command : GoxelCommand
arg0 : UInt8
arg1 : UInt8
deriving Repr, DecidableEq, BEq
namespace GoxelBusPacket
/-- Encode into one braid serial packet. Payload is exactly four bytes. -/
def toSerialPacket (p : GoxelBusPacket) : SerialPacket :=
{ header := { packetType := p.command.toByte, seqNum := p.source.seqNum, length := 4 }
, payload := PacketPayload.fromBytes [p.target.row, p.target.col, p.arg0, p.arg1]
, bracket := BraidBracket.BraidBracket.zero
, residual := Q16_16.zero }
def byteAt (bytes : List UInt8) (idx : Nat) : UInt8 :=
(bytes[idx]?).getD 0
/-- Decode from one braid serial packet. Missing payload bytes fail closed to 0. -/
def fromSerialPacket (pkt : SerialPacket) : GoxelBusPacket :=
{ source := GoxelAddr.fromSeqNum pkt.header.seqNum
, target := { row := byteAt pkt.payload.bytes 0, col := byteAt pkt.payload.bytes 1 }
, command := GoxelCommand.ofByte pkt.header.packetType
, arg0 := byteAt pkt.payload.bytes 2
, arg1 := byteAt pkt.payload.bytes 3 }
def encodeFrame (p : GoxelBusPacket) (frameNum : UInt32) : BraidFrame :=
encodePacket p.toSerialPacket frameNum
def decodeFrame (frame : BraidFrame) : GoxelBusPacket × Bool :=
let decoded := BraidSerial.decodeFrame frame
(fromSerialPacket decoded.1, decoded.2)
end GoxelBusPacket
/-- Apply a command to one cell. -/
def applyCommand (cell : GoxelCell) (cmd : GoxelCommand) (arg0 arg1 : UInt8) : GoxelCell :=
let nextTicks := cell.ticks + 1
match cmd with
| GoxelCommand.ping =>
{ cell with ticks := nextTicks }
| GoxelCommand.activate =>
{ cell with
phase := GoxelPhase.active
scalar := Q16_16.ofRatio arg0.toNat 255
residual := Q16_16.ofRatio arg1.toNat 255
ticks := nextTicks }
| GoxelCommand.settle =>
{ cell with phase := GoxelPhase.settled, ticks := nextTicks }
| GoxelCommand.hold =>
{ cell with phase := GoxelPhase.hold, ticks := nextTicks }
/--
One local bus step. Addressed packets are consumed by the matching cell;
non-addressed packets are forwarded unchanged.
-/
def cellBusStep (cell : GoxelCell) (pkt : GoxelBusPacket) : GoxelCell × Option GoxelBusPacket :=
if cell.addr == pkt.target then
(applyCommand cell pkt.command pkt.arg0 pkt.arg1, none)
else
(cell, some pkt)
/-- A serial goxel grid is stored row-major. -/
structure GoxelGrid where
rows : Nat
cols : Nat
cells : List GoxelCell
deriving Repr, DecidableEq, BEq
namespace GoxelGrid
def addrOfIndex (cols idx : Nat) : GoxelAddr :=
{ row := UInt8.ofNat (idx / cols)
, col := UInt8.ofNat (idx % cols) }
def mkIdle (rows cols : Nat) : GoxelGrid :=
let count := rows * cols
let cells := (List.range count).map (fun idx => GoxelCell.idle (addrOfIndex cols idx))
{ rows := rows, cols := cols, cells := cells }
def cellAt (g : GoxelGrid) (idx : Nat) : Option GoxelCell :=
g.cells[idx]?
/-- Route one packet through the row-major serial bus. -/
def routePacket (g : GoxelGrid) (pkt : GoxelBusPacket) : GoxelGrid × Option GoxelBusPacket :=
let rec go (current : GoxelBusPacket) : List GoxelCell → List GoxelCell × Option GoxelBusPacket
| [] => ([], some current)
| cell :: rest =>
let stepped := cellBusStep cell current
match stepped.2 with
| none => (stepped.1 :: rest, none)
| some forwarded =>
let tail := go forwarded rest
(stepped.1 :: tail.1, tail.2)
let routed := go pkt g.cells
({ g with cells := routed.1 }, routed.2)
end GoxelGrid
/-- Sample addressed packet from cell (0,0) to cell (0,1). -/
def witnessActivatePacket : GoxelBusPacket :=
{ source := { row := 0, col := 0 }
, target := { row := 0, col := 1 }
, command := GoxelCommand.activate
, arg0 := 128
, arg1 := 7 }
/-- Encoding through the internal serial bus preserves the target address. -/
theorem serialRoundtripPreservesTarget :
let decoded := (GoxelBusPacket.decodeFrame (GoxelBusPacket.encodeFrame witnessActivatePacket 4)).1
decoded.target = witnessActivatePacket.target := by
native_decide
/-- Encoding through the internal serial bus preserves the command. -/
theorem serialRoundtripPreservesCommand :
let decoded := (GoxelBusPacket.decodeFrame (GoxelBusPacket.encodeFrame witnessActivatePacket 4)).1
decoded.command = witnessActivatePacket.command := by
native_decide
/-- A targeted packet activates the target cell and is consumed. -/
theorem targetedPacketActivatesAndConsumes :
let routed := GoxelGrid.routePacket (GoxelGrid.mkIdle 1 2) witnessActivatePacket
routed.2 = none ∧ (routed.1.cellAt 1).map (fun c => c.phase) = some GoxelPhase.active := by
native_decide
/-- A packet with no matching address stays on the serial bus. -/
theorem unmatchedPacketForwards :
let pkt : GoxelBusPacket :=
{ source := { row := 0, col := 0 }
, target := { row := 9, col := 9 }
, command := GoxelCommand.activate
, arg0 := 1
, arg1 := 0 }
(GoxelGrid.routePacket (GoxelGrid.mkIdle 1 2) pkt).2 = some pkt := by
native_decide
/-- HOLD is an explicit phase, not an implicit failure. -/
theorem holdCommandSetsHoldPhase :
let pkt : GoxelBusPacket :=
{ source := { row := 0, col := 0 }
, target := { row := 0, col := 0 }
, command := GoxelCommand.hold
, arg0 := 0
, arg1 := 0 }
let routed := GoxelGrid.routePacket (GoxelGrid.mkIdle 1 1) pkt
(routed.1.cellAt 0).map (fun c => c.phase) = some GoxelPhase.hold := by
native_decide
#eval (GoxelBusPacket.decodeFrame (GoxelBusPacket.encodeFrame witnessActivatePacket 4)).1.command
#eval (GoxelGrid.routePacket (GoxelGrid.mkIdle 1 2) witnessActivatePacket).2
#eval (GoxelGrid.routePacket (GoxelGrid.mkIdle 1 2) witnessActivatePacket).1.cellAt 1 |>.map (fun c => c.phase)
end Semantics.GoxelGridBus