From 67f3ff241e0051f0d90ef505c24d19a48134f9f1 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 10:16:52 +0700 Subject: [PATCH] spec(t27f): raise SpecGraph capacity to 192 nodes and 254 edges (Closes #10180) Measured on the Railway lab over 2204 specs: top-level declarations per spec p50 20, p90 66, p95 96, p98 183; edges p95 at most 128 on an 86-spec sample. MAX_NODES 20 -> 192, MAX_EDGES 24 -> 254 (u8 endpoints, NO_INDEX 255). Cap-relative refusal tests, an at-capacity valid test in spec_graph, generated at-cap and over-cap sources in the exporter, a node-capacity layout test in the view, and an invariant that the caps cover the measured p95. Co-Authored-By: Claude Sonnet 5.5 --- .claude/skills/t27f/SKILL.md | 4 +- specs/t27f/spec_graph.t27 | 107 +++++++++++++++++++++++++------ specs/t27f/spec_graph_export.t27 | 33 +++++----- specs/t27f/spec_graph_view.t27 | 26 ++++++++ 4 files changed, 132 insertions(+), 38 deletions(-) diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 8a9c95c0c3..32f0641a8d 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -60,7 +60,7 @@ the reels. Describing T27F as #1677 is the error this section exists to stop (ow |---|---|---| | Spec -> graph IR schema (the core) | `specs/t27f/spec_graph.t27` (t27#10020) | spec with tests: node / edge kinds, labels, spans, `validate_graph`, line -> node mapping, node -> ui_plan widget, one hand-read fixture (`specs/tutorial/08_modules_and_arrays.t27`) | | Graph view: layout of a spec graph | `specs/t27f/spec_graph_view.t27` (t27#10034) | spec with tests: one column per node kind, size / colour / dash / row order from the inference label, UNKNOWN nodes alone in a band, every edge with a source line, node -> widget through `node_widget`; numbers only, laid out on the hand-read fixture; drawn by the MCP tool `t27f_graph` (gHashTag/t27-mcp#9) | -| Spec -> graph IR exporter | `specs/t27f/spec_graph_export.t27` (t27#10162) | spec with tests: `export_graph(path, src)` is a deterministic byte scanner over the source, `export_verdict` is `validate_graph` of the result. One node per top-level declaration (a leading `pub ` is allowed), spans to the matching closing brace, every node and edge EXACT or (unclassifiable) OPAQUE + UNKNOWN, CALLS / USES / CONSTRAINS edges with the line of the use, refusal past 20 nodes or 24 edges. On the tutorial 08 snapshot it equals the hand-read fixture on all 14 declaration nodes and all 9 EXACT edges; it does not emit the fixture's `std.testing` UNKNOWN node, nor its 7 DERIVED edges (4 data flow, 3 tested_by), because a scanner never derives. Limits: textual matching without scope or type resolution, no block comments, no struct/enum bodies, no cross-spec `use` resolution, capacity of IR version 1 (most real specs are refused), takes a `[]const u8` slice rather than `[65536]u8` plus a length. Not wired to a host: the MCP tool does not call it. AST/IR audit is trinity#1650, pilot choice #1651 | +| Spec -> graph IR exporter | `specs/t27f/spec_graph_export.t27` (t27#10162) | spec with tests: `export_graph(path, src)` is a deterministic byte scanner over the source, `export_verdict` is `validate_graph` of the result. One node per top-level declaration (a leading `pub ` is allowed), spans to the matching closing brace, every node and edge EXACT or (unclassifiable) OPAQUE + UNKNOWN, CALLS / USES / CONSTRAINS edges with the line of the use, refusal past 192 nodes or 254 edges. On the tutorial 08 snapshot it equals the hand-read fixture on all 14 declaration nodes and all 9 EXACT edges; it does not emit the fixture's `std.testing` UNKNOWN node, nor its 7 DERIVED edges (4 data flow, 3 tested_by), because a scanner never derives. Limits: textual matching without scope or type resolution, no block comments, no struct/enum bodies, no cross-spec `use` resolution, capacity 192 nodes and 254 edges (about 98 percent of specs in specs/ fit; the rest are refused), takes a `[]const u8` slice rather than `[65536]u8` plus a length. Not wired to a host: the MCP tool does not call it. AST/IR audit is trinity#1650, pilot choice #1651 | | UI plan IR + trusted widget catalog | `specs/t27f/ui_plan.t27` (t27#9962) | spec with tests; no renderer draws a plan yet | | Honeycomb focus+context geometry | `specs/t27f/reel.t27` | spec; stills drawn by the MCP tool `t27f_view` | | Renderer in the Trinity UI | gHashTag/trinity | **missing**; no T27F widget is live | @@ -85,7 +85,7 @@ is not progress on the core. row of that table. Nothing is drawn that the table does not back. 4. Say plainly that this is a manual reading unless a graph came out of `export_graph` (`specs/t27f/spec_graph_export.t27`). The exporter reads declarations, spans and exact - CALLS / USES / CONSTRAINS edges only, within 20 nodes and 24 edges; DERIVED edges (tested_by, + CALLS / USES / CONSTRAINS edges only, within 192 nodes and 254 edges; DERIVED edges (tested_by, data flow), UNKNOWN regions and anything larger are still a manual reading, and no host runs the exporter yet. Name that gap rather than hide it. diff --git a/specs/t27f/spec_graph.t27 b/specs/t27f/spec_graph.t27 index 1b49778941..0158c8e01d 100644 --- a/specs/t27f/spec_graph.t27 +++ b/specs/t27f/spec_graph.t27 @@ -107,8 +107,18 @@ pub const EDGE_FROM_KINDS : [5]u32 = [60, 61, 4, 16, 198]; pub const EDGE_TO_KINDS : [5]u32 = [4, 66, 56, 6, 188]; /// Capacity. Line 0 means "no source line": a span with line_start 0 is no span. -pub const MAX_NODES : u32 = 20; -pub const MAX_EDGES : u32 = 24; +/// Measured 2026-10-18 on the Railway lab, over the 2204 specs/**/*.t27 files of gHashTag/t27 +/// master (t27#10173 tree): top-level declarations per spec (nodes) p50 20, p90 66, p95 96, +/// p98 183, p99 436, max 678. Call/use/constrain edges, read through the exporter on an 86-spec +/// stratified sample: p50 at most 12, p95 at most 128 (82 of 86 specs), 85 of 86 at most 192, the +/// largest between 193 and 256. The caps cover the node p98 and the edge p95. Node and edge +/// endpoints are u8 and NO_INDEX is 255, so both caps must stay below 255; MAX_EDGES is the +/// largest edge count that does, MAX_NODES is the 192 that covers p98 under the same +/// limit. A larger graph is refused, never truncated (validate_graph). +pub const MAX_NODES : u32 = 192; +pub const MAX_EDGES : u32 = 254; +pub const MEASURED_NODES_P95 : u32 = 96; +pub const MEASURED_EDGES_P95 : u32 = 128; pub const NO_LINE : u32 = 0; pub const NO_INDEX : u32 = 255; @@ -143,17 +153,17 @@ pub struct SpecGraph { spec_path: str, line_count: u32, node_count: u32, - node_kind: [20]u8, - node_label: [20]u8, - node_name: [20]str, - line_start: [20]u32, - line_end: [20]u32, + node_kind: [192]u8, + node_label: [192]u8, + node_name: [192]str, + line_start: [192]u32, + line_end: [192]u32, edge_count: u32, - edge_kind: [24]u8, - edge_from: [24]u8, - edge_to: [24]u8, - edge_label: [24]u8, - edge_line: [24]u32, + edge_kind: [254]u8, + edge_from: [254]u8, + edge_to: [254]u8, + edge_label: [254]u8, + edge_line: [254]u32, } /// One node read out of a graph: the record a node inspector shows. @@ -501,7 +511,26 @@ pub fn all_bindings_trusted() -> bool { /// An empty graph of the current version: no path, no nodes, no edges. pub fn blank_graph() -> SpecGraph { - return SpecGraph { ir_version: GRAPH_IR_VERSION, spec_path: "", line_count: 0, node_count: 0, node_kind: [0; 20], node_label: [0; 20], node_name: ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""], line_start: [0; 20], line_end: [0; 20], edge_count: 0, edge_kind: [0; 24], edge_from: [0; 24], edge_to: [0; 24], edge_label: [0; 24], edge_line: [0; 24] }; + return SpecGraph { + ir_version: GRAPH_IR_VERSION, spec_path: "", line_count: 0, node_count: 0, + node_kind: [0; 192], node_label: [0; 192], + node_name: [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" + ], + line_start: [0; 192], line_end: [0; 192], + edge_count: 0, edge_kind: [0; 254], edge_from: [0; 254], edge_to: [0; 254], edge_label: [0; 254], edge_line: [0; 254] + }; } /// g with one more node. Past MAX_NODES the node is dropped and node_count still counts it, so @@ -717,24 +746,59 @@ test header_rules_come_first { test capacity_is_refused_not_truncated { var g : SpecGraph = tutorial_08_graph(); - var i : u32 = 0; - while (i < 6) { + while (g.node_count < MAX_NODES + 1) { g = with_node(g, NODE_CONST, LABEL_DERIVED, "extra", NO_LINE, NO_LINE); - i = i + 1; } - // 21 nodes declared, 20 stored: the graph is refused, not cut short + // MAX_NODES + 1 nodes declared, MAX_NODES stored: the graph is refused, not cut short assert g.node_count == MAX_NODES + 1; assert validate_graph(g) == ERR_TOO_MANY_NODES; var h : SpecGraph = tutorial_08_graph(); - var j : u32 = 0; - while (j < 9) { + while (h.edge_count < MAX_EDGES + 1) { h = with_edge(h, EDGE_TESTED_BY, 9, 13, LABEL_DERIVED, NO_LINE); - j = j + 1; } assert h.edge_count == MAX_EDGES + 1; assert validate_graph(h) == ERR_TOO_MANY_EDGES; } +test a_graph_exactly_at_capacity_is_valid { + // 16 fns and 16 tests after the fixture's 15 nodes, then consts up to MAX_NODES; every + // test calls every fn, so the CALLS edges are distinct (a repeated edge is a duplicate) + var g : SpecGraph = tutorial_08_graph(); + var a : u32 = 0; + while (a < 16) { + g = with_node(g, NODE_FN, LABEL_EXACT, "f", 1, 1); + a = a + 1; + } + a = 0; + while (a < 16) { + g = with_node(g, NODE_TEST, LABEL_EXACT, "t", 1, 1); + a = a + 1; + } + while (g.node_count < MAX_NODES) { + g = with_node(g, NODE_CONST, LABEL_EXACT, "extra", 1, 1); + } + var t : u32 = 0; + while (t < 16) { + var f : u32 = 0; + while (f < 16) { + if (g.edge_count < MAX_EDGES) { + g = with_edge(g, EDGE_CALLS, (31 + t) as u8, (15 + f) as u8, LABEL_EXACT, 1); + } + f = f + 1; + } + t = t + 1; + } + assert g.node_count == MAX_NODES; + assert g.edge_count == MAX_EDGES; + assert validate_graph(g) == GRAPH_OK; + // the last stored node and edge are real cells, not clipped + assert g.node_kind[MAX_NODES as usize - 1] == NODE_CONST; + assert g.edge_kind[MAX_EDGES as usize - 1] == EDGE_CALLS; + // negative control: one more edge or node makes the same graph refused + assert validate_graph(with_edge(g, EDGE_CALLS, 31, 15, LABEL_EXACT, 1)) == ERR_TOO_MANY_EDGES; + assert validate_graph(with_node(g, NODE_CONST, LABEL_EXACT, "extra", 1, 1)) == ERR_TOO_MANY_NODES; +} + test node_rules { var k : SpecGraph = tutorial_08_graph(); k.node_kind[3] = NODE_KINDS; @@ -1000,6 +1064,9 @@ invariant reserved_codes_stay_outside { assert NO_CODE >= EDGE_KINDS; assert NO_CODE >= LABEL_KINDS; assert MAX_NODES < NO_INDEX; + assert MAX_EDGES < NO_INDEX; + assert MAX_NODES >= MEASURED_NODES_P95; + assert MAX_EDGES >= MEASURED_EDGES_P95; assert (NODE_KINDS as u32) < 32; assert ERR_DUPLICATE_EDGE + 1 == VERDICT_KINDS; } diff --git a/specs/t27f/spec_graph_export.t27 b/specs/t27f/spec_graph_export.t27 index 4150c75739..03a9d8c804 100644 --- a/specs/t27f/spec_graph_export.t27 +++ b/specs/t27f/spec_graph_export.t27 @@ -38,7 +38,8 @@ ; line or an indented continuation line starts a node of its own. A block-form module is not ; descended as a unit: its declarations are read as top-level ones and its closing brace is ; skipped. -; - Capacity is IR version 1's: 20 nodes and 24 edges, so most real specs are refused. +; - Capacity is the IR's: 192 nodes and 254 edges (measured, see spec_graph.t27), which holds about +; 98 of 100 specs in specs/; the larger ones are refused. ; - The scanner takes the source as a byte slice; tutorial 08 is embedded below as a snapshot ; (sha256 c291e925d9dabef8b72b5a64aef153a2126c0b0b43d67bc08b1b2d7eaa98039a) and the test ; compares the snapshot, not the live file. @@ -111,9 +112,9 @@ pub struct LineScan { /// Per node: where its name matches and where its first line starts, as byte offsets into the /// source. Entries at or past node_count are not read. pub struct NameSpans { - ma: [20]usize, - mb: [20]usize, - ia: [20]usize, + ma: [192]usize, + mb: [192]usize, + ia: [192]usize, } pub fn ex_space(c: u8) -> bool { @@ -426,7 +427,7 @@ pub fn export_graph(path: str, src: []const u8) -> SpecGraph { g.spec_path = path; g.line_count = count_lines(src); g = with_node(g, NODE_MODULE, LABEL_UNKNOWN, NO_MODULE_NAME, 1, g.line_count); - var sp : NameSpans = NameSpans { ma: [0; 20], mb: [0; 20], ia: [0; 20] }; + var sp : NameSpans = NameSpans { ma: [0; 192], mb: [0; 192], ia: [0; 192] }; var pos : usize = 0; var line : u32 = 0; var depth : u32 = 0; @@ -849,13 +850,13 @@ test the_scanner_never_emits_derived_or_suggested { } test more_nodes_than_the_ir_holds_is_refused_not_truncated { - // module + 19 consts = 20 nodes: exactly at capacity, valid - const at_cap : []const u8 = "module m;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\n"; + // module + 191 consts = 192 nodes: exactly at capacity, valid + const at_cap : []const u8 = "module m;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\n"; const a : SpecGraph = export_graph("specs/x.t27", at_cap); assert a.node_count == MAX_NODES; assert validate_graph(a) == GRAPH_OK; - // module + 20 consts = 21 nodes: one over, and the count says so - const over : []const u8 = "module m;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\n"; + // module + 192 consts = 193 nodes: one over, and the count says so + const over : []const u8 = "module m;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\nconst a : u8 = 1;\n"; const g : SpecGraph = export_graph("specs/x.t27", over); assert g.node_count == MAX_NODES + 1; assert validate_graph(g) == ERR_TOO_MANY_NODES; @@ -867,17 +868,17 @@ test more_nodes_than_the_ir_holds_is_refused_not_truncated { } test more_edges_than_the_ir_holds_is_refused_not_truncated { - // 3 fns and 8 tests that each call all three: 24 edges, exactly at capacity - const at_cap : []const u8 = "module m;\nfn a() u8 { return 1; }\nfn b() u8 { return 1; }\nfn c() u8 { return 1; }\ntest \"t1\" { a(); b(); c(); }\ntest \"t2\" { a(); b(); c(); }\ntest \"t3\" { a(); b(); c(); }\ntest \"t4\" { a(); b(); c(); }\ntest \"t5\" { a(); b(); c(); }\ntest \"t6\" { a(); b(); c(); }\ntest \"t7\" { a(); b(); c(); }\ntest \"t8\" { a(); b(); c(); }\n"; + // 2 fns and 127 tests that each call both: 254 edges, exactly at capacity + const at_cap : []const u8 = "module m;\nfn a() u8 { return 1; }\nfn b() u8 { return 1; }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\n"; const a : SpecGraph = export_graph("specs/x.t27", at_cap); - assert a.node_count == 12; + assert a.node_count == 130; assert a.edge_count == MAX_EDGES; assert validate_graph(a) == GRAPH_OK; - // a ninth test makes 27: over, and the count says so - const over : []const u8 = "module m;\nfn a() u8 { return 1; }\nfn b() u8 { return 1; }\nfn c() u8 { return 1; }\ntest \"t1\" { a(); b(); c(); }\ntest \"t2\" { a(); b(); c(); }\ntest \"t3\" { a(); b(); c(); }\ntest \"t4\" { a(); b(); c(); }\ntest \"t5\" { a(); b(); c(); }\ntest \"t6\" { a(); b(); c(); }\ntest \"t7\" { a(); b(); c(); }\ntest \"t8\" { a(); b(); c(); }\ntest \"t9\" { a(); b(); c(); }\n"; + // a 128th test makes 256: over, and the count says so + const over : []const u8 = "module m;\nfn a() u8 { return 1; }\nfn b() u8 { return 1; }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\ntest \"t\" { a(); b(); }\n"; const g : SpecGraph = export_graph("specs/x.t27", over); - assert g.node_count == 13; - assert g.edge_count == MAX_EDGES + 3; + assert g.node_count == 131; + assert g.edge_count == MAX_EDGES + 2; assert validate_graph(g) == ERR_TOO_MANY_EDGES; // negative controls: it is the edge verdict, not the node one, and it is not OK assert export_verdict("specs/x.t27", over) == ERR_TOO_MANY_EDGES; diff --git a/specs/t27f/spec_graph_view.t27 b/specs/t27f/spec_graph_view.t27 index 28568b1ed5..d469b1b440 100644 --- a/specs/t27f/spec_graph_view.t27 +++ b/specs/t27f/spec_graph_view.t27 @@ -28,6 +28,8 @@ use t27f::spec_graph::node_widget; use t27f::spec_graph::tutorial_08_graph; use t27f::spec_graph::with_suggestion; use t27f::spec_graph::with_edge; +use t27f::spec_graph::with_node; +use t27f::spec_graph::MAX_NODES; use t27f::spec_graph::blank_graph; use t27f::spec_graph::GRAPH_OK; use t27f::spec_graph::VERDICT_KINDS; @@ -677,6 +679,30 @@ test refused_graphs_are_not_drawn { assert view_verdict(blank_graph()) == ERR_SPEC_PATH; } +test a_graph_at_node_capacity_is_drawn_in_a_tall_canvas { + let base : SpecGraph = tutorial_08_graph(); + var g : SpecGraph = base; + while (g.node_count < MAX_NODES) { + g = with_node(g, NODE_CONST, LABEL_EXACT, "extra", 1, 1); + } + assert g.node_count == MAX_NODES; + assert view_verdict(g) == VIEW_OK; + // one column per kind is unchanged by more nodes of a kind already present + assert view_column_count(g) == view_column_count(base); + assert view_width(g) == view_width(base); + // the const column holds at least 177 more rows, so the canvas grows in height only + assert view_known_depth(g) >= 177; + assert view_height(g) > 11000; + assert view_height(g) > view_height(base); + // the last two extra nodes share a column on different rows: boxes do not overlap + let last : u32 = MAX_NODES - 1; + assert view_node_column(g, last) == view_node_column(g, last - 1); + assert view_node_y(g, last) == view_node_y(g, last - 1) + VIEW_ROW_STEP; + assert view_overlap(g, last, last - 1) == false; + // negative control: the same node drawn on top of itself overlaps + assert view_overlap(g, last, last); +} + test nodes_bind_through_spec_graph { let g : SpecGraph = tutorial_08_graph(); assert view_node_widget(g, 0) == WIDGET_HONEYCOMB_CARD;