Skip to content

Commit 450fe91

Browse files
committed
ogar-blockly: everything is a call — (function : value), shape-carved
Operator-ruled rework of the function body from a flat opcode stream to the V3 indexed reading: every 12-byte lane is 6x(u8:u8) rails indexed against a label codebook, and the unit is a Call -- function index : value byte(s). There is no opcode/function distinction any more. ADD is function 0x40, a user block is another index in the same <256 codebook, and invoking either is the same bytes. PaletteOp is renamed FnIndex; the Inventory SoA is the label codebook those indices resolve against, which is why labels stay out of payloads (slot purity) while one byte still names anything in scope. Three earlier defects/claims retire in place: - The "nesting gap" is withdrawn. It was an artifact of treating the body as self-delimiting bytecode. Nesting is by reference -- a function index names another function's node -- exactly as SB3 nests via block ids. No END marker, no jump offset, no need. - The operand gap (codex P1 on #235) closes. The value byte is the immediate (WAIT:10, REPEAT:4); computed arguments use a stack discipline ((NUMBER:5)(NUMBER:3)(ADD:_)); wide literals spend the value byte as a constant-pool index (pool is a named follow-up). - Arity is a classid property, not an encoding trick. LaneShape (mirroring CascadeShape G6D2/G4D3/G3D4) carves the same 360 bytes as 180 pairs / 120 triples / 90 quads. A function needing more immediates picks a wider carving, never a wider field. Narrowing is loud: BodyError::ValueBeyondShape refuses a call the shape would truncate instead of dropping a byte. Length recovery is call-level, per shape -- the byte-level rposition regression is caught by test in every shape. Both guards were verified by breaking them: the truncation guard fails the suite with "two immediates must not fit Pairs", the len regression with "Quads: trailing bare call lost". The retired edge-block design (12 in-family + 4 out-of-family) is stripped from this crate's docs; slot 1 is documented reserved- zeroed with the retirement named, so the deprecated shape cannot be re-learned from here. Relations ride the payload rails as indexed calls. Also recorded in the ledger: the operator's literal-over-grammar ruling (grammar lines like A = B + C are a projection over the pair stream, never the storage format) and the baby-steps roadmap -- ABI-shaped Blockly/Scratch first, later a PowerAutomate-shaped low-code editor, both Mario-editor ergonomics over ClassView : WideFieldMask projections. Ledger: docs/DISCOVERY-MAP.md D-BLOCKS-PALETTE (correction 2). Gates: 16 tests pass (2 falsifier break-runs verified), fmt clean, clippy -D warnings clean, workspace check + tests clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011DN5odWu4eisdevH9bPWyz
1 parent 49deab6 commit 450fe91

3 files changed

Lines changed: 956 additions & 496 deletions

File tree

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,82 @@
11
//! Layout + density accounting for a block function node.
22
//!
3-
//! Prints the byte budget and the amortized cost per operation at several
4-
//! occupancies, so the density claims in `docs/DISCOVERY-MAP.md`
3+
//! Prints the byte budget and the amortized cost per call at several lane
4+
//! shapes and occupancies, so the density claims in `docs/DISCOVERY-MAP.md`
55
//! `D-BLOCKS-PALETTE` can be re-measured rather than trusted.
66
//!
77
//! ```sh
88
//! cargo run -p ogar-blockly --example density
99
//! ```
1010
1111
use ogar_blockly::{
12-
CLASSID_BYTES, CONTENT_SLOTS, FunctionBody, OPS_PER_FUNCTION, PAYLOAD_BYTES_PER_SLOT,
13-
SLOT_STRIDE, VALUE_SLAB_LEN,
12+
CLASSID_BYTES, CONTENT_SLOTS, FunctionBody, LaneShape, PAYLOAD_BYTES_PER_SLOT, SLOT_STRIDE,
13+
VALUE_SLAB_LEN,
1414
};
1515

16-
/// Bytes of a whole node: key(16) + edges(16) + value(480).
16+
/// Bytes of a whole node: key(16) + reserved(16) + value(480).
1717
const NODE_BYTES: usize = 512;
1818

1919
fn main() {
2020
println!("── node layout ──");
2121
println!(" node {NODE_BYTES} B = 32 × {SLOT_STRIDE} B slots");
2222
println!(" key 16 B (slot 0)");
23-
println!(" edges 16 B (slot 1)");
24-
println!(" value slab {VALUE_SLAB_LEN} B (slots 2..31 = {CONTENT_SLOTS} facets)");
23+
println!(" reserved 16 B (slot 1 — zeroed; retired edge-block NOT revived)");
24+
println!(" value slab {VALUE_SLAB_LEN} B (slots 2..31 = {CONTENT_SLOTS} lanes)");
2525
println!(
2626
" classid overhead {} B ({CONTENT_SLOTS} × {CLASSID_BYTES}, interleaved)",
2727
CONTENT_SLOTS * CLASSID_BYTES
2828
);
2929
println!(
30-
" operation bytes {OPS_PER_FUNCTION} B ({CONTENT_SLOTS} × {PAYLOAD_BYTES_PER_SLOT})"
30+
" call bytes {} B ({CONTENT_SLOTS} × {PAYLOAD_BYTES_PER_SLOT})",
31+
CONTENT_SLOTS * PAYLOAD_BYTES_PER_SLOT
3132
);
3233

34+
println!("\n── lane shapes: same 360 bytes, three carvings ──");
35+
for shape in LaneShape::ALL {
36+
println!(
37+
" {shape:?}: {} B/call ({} immediate{}) → {} calls/lane, {} calls/node",
38+
shape.bytes_per_call(),
39+
shape.values_per_call(),
40+
if shape.values_per_call() == 1 {
41+
""
42+
} else {
43+
"s"
44+
},
45+
shape.calls_per_lane(),
46+
shape.calls_per_function()
47+
);
48+
}
49+
3350
println!("\n── in-memory vs wire ──");
3451
println!(
35-
" FunctionBody {} B ([u8; {OPS_PER_FUNCTION}] + u16 len)",
52+
" FunctionBody {} B ([u8; 360] + u16 len + LaneShape)",
3653
size_of::<FunctionBody>()
3754
);
3855
println!(
39-
" wire payload {OPS_PER_FUNCTION} B (len is NOT written; NOP padding is the signal)"
56+
" wire payload 360 B (len & shape NOT written: padding is length, classid is shape)"
4057
);
4158

42-
println!("\n── amortized cost per operation (whole {NODE_BYTES} B node) ──");
43-
for ops in [OPS_PER_FUNCTION, 180, 90, 30] {
44-
let per_op = NODE_BYTES as f64 / ops as f64;
45-
let occupancy = 100.0 * ops as f64 / OPS_PER_FUNCTION as f64;
46-
println!(" {ops:3} ops ({occupancy:5.1}% full) {per_op:6.3} B/op");
59+
println!("\n── amortized cost per call (whole {NODE_BYTES} B node) ──");
60+
for shape in LaneShape::ALL {
61+
let cap = shape.calls_per_function();
62+
for div in [1usize, 2, 4] {
63+
let calls = cap / div;
64+
let per_call = NODE_BYTES as f64 / calls as f64;
65+
let occupancy = 100.0 / div as f64;
66+
println!(" {shape:?} {calls:3} calls ({occupancy:5.1}% full) {per_call:6.3} B/call");
67+
}
4768
}
4869

49-
println!("\n── the operations are NOT contiguous in the slab ──");
50-
for i in [0usize, 11, 12, 23, 24, OPS_PER_FUNCTION - 1] {
51-
println!(
52-
" op {i:3} → slab offset {:3} (facet {:2}, byte {:2} of its payload lane)",
53-
FunctionBody::slab_offset(i),
54-
i / PAYLOAD_BYTES_PER_SLOT,
55-
i % PAYLOAD_BYTES_PER_SLOT
56-
);
70+
println!("\n── calls are NOT contiguous in the slab ──");
71+
for shape in [LaneShape::Pairs, LaneShape::Quads] {
72+
let cpl = shape.calls_per_lane();
73+
for i in [0usize, cpl - 1, cpl, shape.calls_per_function() - 1] {
74+
println!(
75+
" {shape:?} call {i:3} → slab offset {:3} (lane {:2}, byte {:2} of its payload)",
76+
FunctionBody::call_slab_offset(shape, i),
77+
i / cpl,
78+
(i % cpl) * shape.bytes_per_call()
79+
);
80+
}
5781
}
5882
}

0 commit comments

Comments
 (0)