diff --git a/packages/web/docs/concepts/_category_.json b/packages/web/docs/concepts/_category_.json
index 1d3167d49..d373a2925 100644
--- a/packages/web/docs/concepts/_category_.json
+++ b/packages/web/docs/concepts/_category_.json
@@ -1,4 +1,4 @@
{
"label": "Concepts",
- "position": 3
+ "position": 4
}
diff --git a/packages/web/docs/core-schemas/_category_.json b/packages/web/docs/core-schemas/_category_.json
index 0fbf76d9e..85fb37536 100644
--- a/packages/web/docs/core-schemas/_category_.json
+++ b/packages/web/docs/core-schemas/_category_.json
@@ -1,4 +1,4 @@
{
"label": "Core schemas",
- "position": 4
+ "position": 5
}
diff --git a/packages/web/docs/core-schemas/info/resources.mdx b/packages/web/docs/core-schemas/info/resources.mdx
index 5abb41766..44dce62e9 100644
--- a/packages/web/docs/core-schemas/info/resources.mdx
+++ b/packages/web/docs/core-schemas/info/resources.mdx
@@ -1,6 +1,5 @@
---
sidebar_position: 2
-pagination_next: examples/index
---
import SpecLink from "@site/src/components/SpecLink";
diff --git a/packages/web/docs/core-schemas/pointers/collections.mdx b/packages/web/docs/core-schemas/pointers/collections.mdx
index dc46151d1..1dec6ed79 100644
--- a/packages/web/docs/core-schemas/pointers/collections.mdx
+++ b/packages/web/docs/core-schemas/pointers/collections.mdx
@@ -3,7 +3,6 @@ sidebar_position: 4
---
import SpecLink from "@site/src/components/SpecLink";
-import { PointerPlayground, PointerExample } from "@theme/PointersExample";
# Collections
@@ -12,11 +11,13 @@ import { PointerPlayground, PointerExample } from "@theme/PointersExample";
href="/spec/pointer/collection"
/>
-
+While [regions](./regions) describe single contiguous byte ranges,
+**collections** aggregate multiple pointers together. Collections handle
+cases where data structures span multiple locations or have dynamic
+configurations.
-While [regions](./regions) describe single contiguous byte ranges, **collections**
-aggregate multiple pointers together. Collections handle cases where data
-structures span multiple locations or have dynamic configurations.
+To build and resolve collection pointers interactively, see the
+[Pointer playground](/docs/explore/pointer-playground).
## Why collections?
@@ -36,34 +37,12 @@ Collections provide six patterns for composing pointers:
| `reference` | Invoke reusable pointer templates |
| `templates` | Define inline templates for local reuse |
-Click **"▶ Try it"** on any example to load it into the Pointer Playground
-drawer at the bottom of the screen.
-
## Group
A **group** combines multiple pointers into a single composite pointer. Each
-pointer in the group can have a name for identification.
-
-
-
-Groups are useful for structs and other compound data types where multiple
-fields need to be accessed together.
+pointer in the group can have a name for identification. Groups are useful for
+structs and other compound data types where multiple fields need to be
+accessed together.
## List
@@ -77,68 +56,10 @@ identifiers with types and pointers). See the
[glossary](/docs/reference/glossary) for complete definitions.
:::
-
-
The list evaluates `count` to determine how many pointers to generate, then
for each index (bound to the variable named by `each`), it evaluates the `is`
-pointer template.
-
-### List with dynamic count
-
-When the count comes from storage:
-
-
+pointer template. The `count` can itself come from storage, read via `$read`,
+so the number of elements is determined at runtime.
### List properties
@@ -153,31 +74,6 @@ When the count comes from storage:
A **conditional** selects between pointers based on whether an expression
evaluates to a non-zero value.
-
-
### Conditional properties
| Property | Description |
@@ -191,28 +87,6 @@ evaluates to a non-zero value.
A **scope** defines variables that can be used in a nested pointer. Variables
are evaluated in order, so later variables can reference earlier ones.
-
-
Scopes help break complex pointer definitions into readable steps. For examples
combining scopes with keccak256 for storage slot computation, see the
[expressions documentation](./expressions#computing-storage-slots-with-keccak256).
@@ -229,39 +103,6 @@ combining scopes with keccak256 for storage slot computation, see the
A **reference** invokes a named pointer template, while **templates** defines
them inline. These work together for reusable pointer patterns.
-
-
### Template definition
Each template in the `templates` object has:
@@ -284,80 +125,14 @@ Collections can be nested to build complex pointer structures. A group might
contain lists, conditionals might wrap groups, and scopes can define variables
used throughout nested collections.
-
-
## Named regions in collections
Pointers within collections can include a `name` property. Named regions are
tracked during resolution and can be referenced using the `.slot`, `.offset`,
and `.length` syntax in expressions.
-
-
## Learn more
- [Regions](./regions) for simple pointer definitions
- [Expressions](./expressions) for dynamic value computation
- [Pointer specification](/spec/pointer/collection) for formal definitions
-
-
diff --git a/packages/web/docs/core-schemas/pointers/expressions.mdx b/packages/web/docs/core-schemas/pointers/expressions.mdx
index ddca75b23..ed60fb787 100644
--- a/packages/web/docs/core-schemas/pointers/expressions.mdx
+++ b/packages/web/docs/core-schemas/pointers/expressions.mdx
@@ -4,7 +4,6 @@ sidebar_position: 3
import SpecLink from "@site/src/components/SpecLink";
import SchemaExample from "@site/src/components/SchemaExample";
-import { PointerPlayground, PointerExample } from "@theme/PointersExample";
# Expressions
@@ -13,12 +12,13 @@ import { PointerPlayground, PointerExample } from "@theme/PointersExample";
href="/spec/pointer/expression"
/>
-
-
Static offsets work for simple variables, but most interesting data has
locations that depend on runtime values. Expressions let pointers compute
addresses dynamically.
+To evaluate expressions against live EVM state interactively, see the
+[Pointer playground](/docs/explore/pointer-playground).
+
## Why expressions are needed
Consider reading element `i` from a memory array. The element's location
@@ -28,32 +28,9 @@ depends on:
- Which element we want (the index `i`)
- How big each element is (32 bytes for `uint256`)
-A static pointer can't capture this. Expressions can:
-
-
+A static pointer can't capture this, but an expression can — for example,
+`array-start + index × 32` computes the element's offset from the array's
+base and the index.
## Arithmetic expressions
@@ -61,116 +38,31 @@ Basic math operations for computing addresses:
### `$sum` — Addition
-Adds all values in an array:
-
-
+Adds all values in an array.
### `$difference` — Subtraction
-Subtracts the second value from the first (saturates at zero):
-
-
+Subtracts the second value from the first (saturates at zero).
### `$product` — Multiplication
-Multiplies all values in an array:
-
-
+Multiplies all values in an array.
### `$quotient` — Division
-Integer division of first value by second:
-
-
+Integer division of the first value by the second.
### `$remainder` — Modulo
-Remainder after division:
-
-
+Remainder after division.
## Reading values
### `$read` — Read from a named region
-Reads the bytes from a previously defined region:
-
-
-
-The `$read` expression retrieves the actual runtime value stored in the
-`array-length-slot` region—the array's length.
+Reads the bytes from a previously defined region. For example, a group can
+name an `array-length-slot` region and then use `{ $read: "array-length-slot" }`
+to retrieve the array's length at runtime and use it in a later computation.
## Region property lookups
@@ -202,32 +94,9 @@ Returns the slot number for storage/stack/transient regions.
### Chaining lookups
-Compute the next element's position from the previous one:
-
-
+Property lookups can compute the next element's position from the previous
+one. For example, `element-1`'s offset can be computed as the sum of
+`element-0`'s `.offset` and its `.length`.
## Computing storage slots with `$keccak256`
@@ -235,228 +104,43 @@ Solidity uses keccak256 hashing to compute storage locations for dynamic data.
### Array element slots
-For a dynamic array at slot `n`, elements start at `keccak256(n)`:
-
-
+For a dynamic array at slot `n`, elements start at `keccak256(n)`, so element
+`i` lives at `keccak256(n) + i`.
### Mapping value slots
-For a mapping at slot `n`, the value for key `k` is at `keccak256(k, n)`:
-
-
+For a mapping at slot `n`, the value for key `k` is at `keccak256(k, n)`.
### Nested mappings
-For `mapping(address => mapping(uint => uint))` at slot 2:
-
-
-
-This computes: `keccak256(inner_key, keccak256(outer_key, 2))`
+For `mapping(address => mapping(uint => uint))` at slot 2, the value is at
+`keccak256(inner_key, keccak256(outer_key, 2))`.
## Data manipulation
### `$concat` — Concatenate bytes
-Joins byte sequences without padding:
-
-
-
-Useful for building hash inputs from multiple values.
+Joins byte sequences without padding. Useful for building hash inputs from
+multiple values.
### `$sized` — resize to N bytes
-Truncates or pads to exactly N bytes:
-
-
-
-Pads with zeros on the left; truncates from the left if too long.
+Truncates or pads to exactly N bytes. Pads with zeros on the left; truncates
+from the left if too long.
### `$wordsized` — Resize to word size
-Equivalent to `$sized32` on the EVM:
-
-
+Equivalent to `$sized32` on the EVM (pads or truncates to 32 bytes).
## Variables in expressions
Expressions can reference variables by name. These come from list pointer
-contexts:
-
-
-
-The variable `"i"` takes values from 0 to count-1, computing each element's
-slot.
+contexts: within a `list`, the variable named by `each` takes values from 0 to
+count-1, computing each element's slot.
## Complete example: dynamic array element
-Reading element `i` from `uint256[] storage arr` at slot 5:
-
-
-
-The pointer:
+To read element `i` from `uint256[] storage arr` at slot 5, a pointer:
1. Defines the array's base slot
2. Computes the element's slot: `keccak256(5) + element_index`
@@ -469,5 +153,3 @@ The pointer:
expression language
- [Implementation guide](/docs/implementation-guides/pointers/evaluating-expressions)
for building an expression evaluator
-
-
diff --git a/packages/web/docs/core-schemas/pointers/index.mdx b/packages/web/docs/core-schemas/pointers/index.mdx
index db33d46b7..9f9bd9f54 100644
--- a/packages/web/docs/core-schemas/pointers/index.mdx
+++ b/packages/web/docs/core-schemas/pointers/index.mdx
@@ -8,9 +8,9 @@ import SpecLink from "@site/src/components/SpecLink";
-Pointers describe where data lives in the EVM. They're recipes that tell
-debuggers how to find bytes at runtime — recipes that can depend on the
-current machine state.
+A **pointer** in **ethdebug/format** describes where data lives in the
+EVM: a recipe that tells a debugger how to find bytes at runtime — one
+that can depend on the current machine state.
For the mental model behind pointers (including EVM data locations), see
[Concepts: Pointers](/docs/concepts/pointers).
diff --git a/packages/web/docs/core-schemas/pointers/regions.mdx b/packages/web/docs/core-schemas/pointers/regions.mdx
index f03590976..1c9026671 100644
--- a/packages/web/docs/core-schemas/pointers/regions.mdx
+++ b/packages/web/docs/core-schemas/pointers/regions.mdx
@@ -3,18 +3,18 @@ sidebar_position: 2
---
import SpecLink from "@site/src/components/SpecLink";
-import { PointerPlayground, PointerExample } from "@theme/PointersExample";
# Regions
-
-
A region represents a contiguous block of bytes in a specific EVM data
location. Regions are the leaves of the pointer tree—the actual byte ranges
that hold data.
+To resolve regions against live EVM state interactively, see the
+[Pointer playground](/docs/explore/pointer-playground).
+
## Addressing schemes
The EVM uses two different models for organizing bytes, and regions reflect
@@ -25,192 +25,61 @@ this:
**Memory**, **calldata**, **returndata**, and **code** are byte-addressable.
Regions in these locations use `offset` and `length`:
-
-
- `offset`: byte position from the start (required)
- `length`: number of bytes (required)
### Slot-based locations
**Storage**, **transient storage**, and **stack** are organized in 32-byte
-slots. Regions use `slot`:
-
-
+slots. Regions use `slot`.
For storage and transient storage, values that don't fill a full slot can
-specify sub-slot positioning:
-
-
-
-This addresses 20 bytes starting at byte 12 within slot 0—useful for packed
-storage.
+specify sub-slot positioning with `offset` and `length` within the slot —
+useful for packed storage, such as a 20-byte address held at byte 12 of a
+slot.
## Location-specific details
### Memory
-Memory is a simple byte array that grows as needed:
-
-
-
-Memory addresses often come from the free memory pointer (stored at `0x40`).
+Memory is a simple byte array that grows as needed. Memory addresses often
+come from the free memory pointer (stored at `0x40`).
### Storage
Storage persists between transactions. Slots are 32-byte words addressed by
-256-bit keys:
-
-
-
-Slot addresses can be literal numbers, hex strings, or computed expressions.
+256-bit keys. Slot addresses can be literal numbers, hex strings, or computed
+expressions.
### Stack
-The EVM stack holds up to 1024 words. Slot 0 is the top:
-
-
-
-Stack regions are typically read-only from a debugging perspective—you observe
-values but don't address sub-ranges.
+The EVM stack holds up to 1024 words; slot 0 is the top. Stack regions are
+typically read-only from a debugging perspective—you observe values but don't
+address sub-ranges.
### Calldata
-Function arguments arrive in calldata, read-only and byte-addressable:
-
-
-
-The first 4 bytes are typically the function selector; arguments follow.
+Function arguments arrive in calldata, read-only and byte-addressable. The
+first 4 bytes are typically the function selector; arguments follow.
### Returndata
-After a call, the returned data is accessible:
-
-
+After a call, the data it returned is accessible as a byte-addressable
+region.
### Code
-Contract bytecode can be read as data:
-
-
-
-This is used for immutable variables and other data embedded in bytecode.
+Contract bytecode can be read as data. This is used for immutable variables
+and other data embedded in bytecode.
### Transient storage
-Transient storage (EIP-1153) persists only within a transaction:
-
-
-
-Uses the same slot-based addressing as regular storage.
+Transient storage (EIP-1153) persists only within a transaction and uses the
+same slot-based addressing as regular storage.
## Naming regions
-Any region can have a `name` that other parts of the pointer reference:
-
-
-
+Any region can have a `name` that other parts of the pointer reference.
Names enable:
- Reading the region's value with `{ "$read": "token-balance" }`
@@ -230,5 +99,3 @@ reading, see [expressions](./expressions).
For complete schemas for each location type, see the
[pointer region specification](/spec/pointer/region).
-
-
diff --git a/packages/web/docs/core-schemas/programs/tracing.mdx b/packages/web/docs/core-schemas/programs/tracing.mdx
index d901224fb..67e2e234a 100644
--- a/packages/web/docs/core-schemas/programs/tracing.mdx
+++ b/packages/web/docs/core-schemas/programs/tracing.mdx
@@ -4,26 +4,16 @@ sidebar_position: 4
import SpecLink from "@site/src/components/SpecLink";
import SchemaExample from "@site/src/components/SchemaExample";
-import { TracePlayground, TraceExample } from "@theme/ProgramExample";
-import {
- counterIncrement,
- thresholdCheck,
- multipleStorageSlots,
- functionCallAndReturn,
- mutualRecursion,
- tailRecursiveSum,
- tailRecursiveFactorial,
-} from "./tracing-examples";
# Tracing execution
-
-
-Tracing brings together programs, pointers, and types to show what's happening
-at each step of EVM execution. Click **"Try it"** on any example to open the
-Trace Playground, where you can compile and step through real BUG code.
+Tracing brings together programs, pointers, and types to show what's
+happening at each step of EVM execution. This page is the reference for how
+trace data maps onto **ethdebug/format**. To compile and step through real
+BUG programs interactively, see the
+[Trace playground](/docs/explore/trace-playground).
## What tracing provides
@@ -46,77 +36,17 @@ At each instruction in a transaction trace:
current value
4. **Decode values**: Use the type information to interpret raw bytes
-## Try it yourself
-
-Click **"Try it"** on any example below to load it into the Trace Playground.
-The drawer will open at the bottom of the screen where you can compile the
-code and step through the execution trace.
-
-### Simple counter increment
-
-This example shows a basic counter that increments a storage variable:
-
-
-
-### Storage with threshold check
-
-This example demonstrates conditional logic with storage variables:
-
-
-
-### Multiple storage slots
-
-This example shows working with multiple storage locations:
+## Call contexts across function boundaries
-
+When execution crosses a function boundary, the JUMP instructions at the
+boundary carry **invoke** and **return** contexts, so a debugger can follow
+the call and maintain a call stack.
-## Tracing through a function call
+### Entering a function
-The examples above trace simple straight-line code. Real programs
-make function calls. **invoke** and **return** contexts let a
-debugger follow execution across function boundaries.
-
-Click **"Try it"** on the example below, then step through the
-trace. Watch for **invoke** contexts on the JUMP into `add` and
-**return** contexts on the JUMP back to the caller:
-
-
-
-Mutual recursion produces alternating invoke/return pairs. In
-this example, `isEven` and `isOdd` call each other, bouncing
-back and forth until `n` reaches zero. Each call adds a frame
-to the call stack:
-
-
-
-As you step through, three phases are visible:
-
-### Before the call — setting up arguments
-
-At the call site, the compiler pushes arguments onto the stack and
-prepares the jump. The JUMP instruction carries an **invoke**
-context identifying the function, its target, and the argument
-locations:
+At the call site, the compiler pushes arguments onto the stack and prepares
+the jump. The JUMP instruction carries an **invoke** context identifying the
+function, its target, and the argument locations:
-The debugger now knows it's entering `add` with arguments at stack
-slots 2 and 3. A trace viewer can show `add(3, 4)` in the call
-stack.
-
-### Inside the function — normal tracing
+The debugger now knows it's entering `add` with arguments at stack slots 2
+and 3, and can show `add(3, 4)` in the call stack.
-Inside `add`, instructions carry their own `code` and `variables`
-contexts as usual. The debugger shows the source range within the
-function body, and parameters `a` and `b` appear as in-scope
-variables.
+Inside the function, instructions carry their own `code` and `variables`
+contexts as usual — the debugger shows the source range within the function
+body, and parameters appear as in-scope variables.
-### Returning — the result
+### Returning a result
-When `add` finishes, the JUMP back to the caller carries a
-**return** context with a pointer to the result:
+When the function finishes, the JUMP back to the caller carries a **return**
+context with a pointer to the result:
-The debugger pops `add` from the call stack and can display the
-return value (7).
+The debugger pops `add` from the call stack and can display the return
+value.
### External calls and reverts
-The same pattern applies to external message calls, but with
-additional fields. An external CALL instruction carries gas, value,
-and input data pointers:
+The same pattern applies to external message calls, but with additional
+fields. An external CALL instruction carries gas, value, and input data
+pointers:
-For built-in assertion failures, the compiler can provide a panic
-code instead of (or alongside) a reason pointer:
+For built-in assertion failures, the compiler can provide a panic code
+instead of (or alongside) a reason pointer:
-## Tail-call optimization
-
-The recursion examples above push a new frame for every call — step
-through them and watch the call stack grow. A compiler can often avoid
-that. When a recursive call sits in **tail position** — its result is
-returned directly, with no further work after it — the compiler can
-reuse the current frame instead of pushing a new one. This is
-**tail-call optimization** (TCO), and it turns recursion into a loop.
-
-The two programs below are written so bugc's optimizer folds them. Each
-accumulates its result in an `acc` parameter and hands it to the next
-call in tail position, so no work is left pending on the stack.
-
-Use the **Opt** selector in the trace drawer to set the optimization
-level. Compile at **O0** (no optimization) and again at **O2**
-(optimizations on, including TCO), then step through and compare the
-call stack. TCO kicks in at **O2**.
-
-
-
-
-
-At **O0**, each `sum`/`fact` call is a real invoke/return pair and the
-call stack grows one frame per iteration. At **O2**, the recursive
-call becomes a **back-edge**: a single JUMP that ends one iteration and
-begins the next without pushing a frame. The call stack stays flat.
-
-That one JUMP carries three facts at once, composed as sibling keys on a
-single context — the flat form described on the
-[transform context](/spec/program/context/transform) page:
+## Optimized code: the tailcall transform
+
+When the compiler optimizes tail-recursive calls, it turns the recursion
+into a loop: the recursive call becomes a **back-edge** — a single JUMP that
+ends one iteration and begins the next without pushing a frame. That one
+JUMP carries three facts at once, composed as sibling keys on a single
+context (the flat form described on the
+[transform context](/spec/program/context/transform) page):
diff --git a/packages/web/docs/examples/_category_.json b/packages/web/docs/examples/_category_.json
deleted file mode 100644
index 73d20c19a..000000000
--- a/packages/web/docs/examples/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Examples",
- "position": 6
-}
diff --git a/packages/web/docs/examples/index.mdx b/packages/web/docs/examples/index.mdx
deleted file mode 100644
index edd07f4ad..000000000
--- a/packages/web/docs/examples/index.mdx
+++ /dev/null
@@ -1,22 +0,0 @@
----
-sidebar_position: 1
-pagination_prev: core-schemas/info/resources
----
-
-# Examples
-
-Interactive examples demonstrating **ethdebug/format** concepts.
-
-## Available examples
-
-- [**BUG Playground**](/docs/examples/bug-playground) — Interactive
- compiler playground for the BUG language, showing AST, IR, CFG, and
- bytecode views.
-- [**Pointer regions**](/docs/core-schemas/pointers/regions) —
- Interactive pointer playground embedded in the regions reference
- page. Visualize how regions resolve against EVM state for each
- data location.
-- [**Tracing execution**](/docs/core-schemas/programs/tracing) —
- Interactive trace playground embedded in the tracing reference
- page. Compile and step through BUG programs to see source mapping,
- variables, and context in action.
diff --git a/packages/web/docs/explore/_category_.json b/packages/web/docs/explore/_category_.json
new file mode 100644
index 000000000..cc5d43602
--- /dev/null
+++ b/packages/web/docs/explore/_category_.json
@@ -0,0 +1,4 @@
+{
+ "label": "Explore",
+ "position": 8
+}
diff --git a/packages/web/docs/examples/bug-playground.mdx b/packages/web/docs/explore/bug-playground.mdx
similarity index 64%
rename from packages/web/docs/examples/bug-playground.mdx
rename to packages/web/docs/explore/bug-playground.mdx
index 9db8813b6..08949a746 100644
--- a/packages/web/docs/examples/bug-playground.mdx
+++ b/packages/web/docs/explore/bug-playground.mdx
@@ -1,15 +1,24 @@
---
-sidebar_position: 2
+sidebar_position: 4
+sidebar_label: "compile a contract end to end"
---
# BUG playground
-BUG is a minimal smart contract language designed for demonstrating and testing
-**ethdebug/format**. It compiles to EVM bytecode and produces rich debug
-information.
+**BUG** is a minimal smart contract language for exercising
+**ethdebug/format**: small enough to read at a glance, expressive enough to
+reach the cases a debug format has to handle. **bugc** is its reference
+compiler — an _optimizing_ compiler from BUG to EVM bytecode that emits
+**ethdebug/format** debug information alongside the code.
-Use the interactive playground below to explore how BUG code compiles to
-intermediate representations and EVM bytecode.
+Why a reference compiler? Because a format is only as real as something
+that produces it. **bugc** validates **ethdebug/format** by generating it
+for every program it compiles — and you're looking at that validation
+right now: the debug annotations in the **Bytecode** view below are
+**bugc**'s output, the format describing real compiled code.
+
+Edit the source, switch views, and watch each stage update — from parse
+tree to annotated bytecode.
import { BugPlayground } from "@theme/BugcExample";
diff --git a/packages/web/docs/explore/index.mdx b/packages/web/docs/explore/index.mdx
new file mode 100644
index 000000000..5b4cfade8
--- /dev/null
+++ b/packages/web/docs/explore/index.mdx
@@ -0,0 +1,26 @@
+---
+sidebar_position: 1
+---
+
+# Explore
+
+Learn **ethdebug/format** by doing. These interactive playgrounds let you
+compile real code and watch the format describe it — where data lives, how
+execution moves, and what the compiler did along the way. Each one runs in
+your browser; no setup required.
+
+## Playgrounds
+
+- [**Pointer playground**](/docs/explore/pointer-playground) — Watch
+ pointers resolve against concrete EVM state. See how a memory range, a
+ storage slot, a struct, and a computed location each select their bytes.
+- [**Trace playground**](/docs/explore/trace-playground) — Compile a BUG
+ program and step through its execution trace: source location, in-scope
+ variables, the call stack, and instruction context at every step —
+ including the optimizer's tail-call transform.
+- [**BUG playground**](/docs/explore/bug-playground) — Compile the BUG
+ language end to end and inspect each stage: AST, IR, control-flow graph,
+ and bytecode.
+
+Looking for the schema details behind these? See the
+[Core Schemas](/docs/core-schemas/pointers) reference.
diff --git a/packages/web/docs/explore/pointer-playground.mdx b/packages/web/docs/explore/pointer-playground.mdx
new file mode 100644
index 000000000..4baa3bf4a
--- /dev/null
+++ b/packages/web/docs/explore/pointer-playground.mdx
@@ -0,0 +1,249 @@
+---
+sidebar_position: 2
+sidebar_label: "explore high-level data with pointers"
+---
+
+import { PointerPlayground, PointerExample } from "@theme/PointersExample";
+
+export const solidityStringPointer = {
+ define: { slot: 0 },
+ in: {
+ group: [
+ {
+ name: "length-flag",
+ location: "storage",
+ slot: "slot",
+ offset: 31,
+ length: 1,
+ },
+ {
+ if: { $remainder: [{ $sum: [{ $read: "length-flag" }, 1] }, 2] },
+ then: {
+ define: {
+ "string-length": { $quotient: [{ $read: "length-flag" }, 2] },
+ },
+ in: {
+ name: "string",
+ location: "storage",
+ slot: "slot",
+ offset: 0,
+ length: "string-length",
+ },
+ },
+ else: {
+ group: [
+ {
+ name: "long-string-length-data",
+ location: "storage",
+ slot: "slot",
+ offset: 0,
+ length: 32,
+ },
+ {
+ define: {
+ "string-length": {
+ $quotient: [
+ { $difference: [{ $read: "long-string-length-data" }, 1] },
+ 2,
+ ],
+ },
+ "start-slot": { $keccak256: [{ $wordsized: "slot" }] },
+ "total-slots": {
+ $quotient: [
+ { $sum: ["string-length", { $difference: [32, 1] }] },
+ 32,
+ ],
+ },
+ },
+ in: {
+ list: {
+ count: "total-slots",
+ each: "i",
+ is: {
+ define: {
+ "current-slot": { $sum: ["start-slot", "i"] },
+ "previous-length": { $product: ["i", 32] },
+ },
+ in: {
+ if: {
+ $difference: [
+ "string-length",
+ { $sum: ["previous-length", 32] },
+ ],
+ },
+ then: {
+ name: "string",
+ location: "storage",
+ slot: "current-slot",
+ },
+ else: {
+ name: "string",
+ location: "storage",
+ slot: "current-slot",
+ offset: 0,
+ length: {
+ $difference: ["string-length", "previous-length"],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+};
+
+# Pointer playground
+
+A **[pointer](/docs/core-schemas/pointers)** in **ethdebug/format** is a
+recipe for finding data in the EVM: where bytes live, and how to compute
+that location from the current machine state. The
+examples below resolve real Solidity storage layouts against concrete
+state — click **"Try it"** to open the playground and watch each pointer
+select its bytes.
+
+They build up from a single packed variable to the full short/long string
+layout, where the format really earns its keep.
+
+
+
+## A packed storage variable
+
+Solidity packs small values so several share a slot. This pointer reads a
+20-byte `address` stored at byte 12 of slot 0 — a sub-slot region, offset
+and length inside a single word.
+
+
+
+## A dynamic array element
+
+A `uint256[]` at slot 5 stores its elements starting at `keccak256(5)`, so
+element `i` lives at `keccak256(5) + i`. This pointer computes the slot for
+element 2.
+
+
+
+## A mapping value
+
+A `mapping(address => uint256)` at slot 3 stores each value at
+`keccak256(key, 3)`. This pointer reads the key from the stack, hashes it
+with the slot, and resolves the value's location.
+
+
+
+## A Solidity storage string
+
+Strings are the real test. Solidity packs a **short** string (31 bytes or
+fewer) directly into its slot, with `2 × length` in the slot's last byte. A
+**long** string instead stores `2 × length + 1` in the slot and puts the
+data at `keccak256(slot)`, spanning as many following slots as it needs.
+
+One pointer handles both: it reads the last byte, and the parity of that
+flag selects the short branch or the long branch. It's the same pointer in
+both examples below — only the state changes.
+
+### Short string
+
+Slot 0 packs the bytes of `"Hello"` with `0x0a` (that's `2 × 5`) in its
+last byte. The flag is even, so the pointer reads the string straight out
+of the slot.
+
+
+
+### Long string
+
+Here the flag is `0x51` (`2 × 40 + 1`), so the pointer takes the long
+branch: it computes the length as `(0x51 − 1) / 2 = 40`, finds the data at
+`keccak256(0)`, and reads 40 bytes across that slot and the next.
+
+
+
+## Learn more
+
+- [Regions](/docs/core-schemas/pointers/regions) for locations and
+ addressing schemes
+- [Collections](/docs/core-schemas/pointers/collections) for groups,
+ lists, and conditionals
+- [Expressions](/docs/core-schemas/pointers/expressions) for arithmetic,
+ reads, and `$keccak256`
+
+
diff --git a/packages/web/docs/explore/trace-playground.mdx b/packages/web/docs/explore/trace-playground.mdx
new file mode 100644
index 000000000..01607bbd8
--- /dev/null
+++ b/packages/web/docs/explore/trace-playground.mdx
@@ -0,0 +1,81 @@
+---
+sidebar_position: 3
+sidebar_label: "step through an execution trace"
+---
+
+import { TracePlayground, TraceExample } from "@theme/ProgramExample";
+import {
+ counterIncrement,
+ functionCallAndReturn,
+ tailRecursiveSum,
+ tailRecursiveFactorial,
+} from "../core-schemas/programs/tracing-examples";
+
+# Trace playground
+
+See **ethdebug/format** in motion. Each example below compiles a small
+BUG program and lets you step through its execution trace — watching the
+source line, in-scope variables, call stack, and instruction context
+light up at every step.
+
+Click **"Try it"** on an example to open the trace drawer, then use the
+step controls to walk through execution. The examples build on each
+other: start at the top and work down, picking up one idea at a time.
+
+
+
+## Start here: a storage write
+
+`Counter` increments a single storage slot. Step through it to get a feel
+for the drawer — the highlighted source line, the value in storage, and
+the context attached to each instruction.
+
+
+
+## Following a function call
+
+Real programs call functions, and the debugger follows them. Watch the
+call stack push a frame on the JUMP into `add` (an **invoke** context)
+and pop it on the JUMP back (a **return** context).
+
+
+
+For the exact shape of invoke, return, and revert contexts, see the
+[function call spec](/spec/program/context/function/invoke) and the
+[tracing reference](/docs/core-schemas/programs/tracing).
+
+## Watching the optimizer
+
+Compilers rewrite code as they optimize, and **transform** contexts
+record what they did. Set the **Opt** selector to **O2** and step through
+these tail-recursive programs: the recursive call folds into a loop, and
+the back-edge JUMP carries `transform: ["tailcall"]` next to its invoke
+and return. The call stack stays flat instead of growing one frame per
+iteration.
+
+
+
+
+
+Flip the **Opt** selector between **O0** and **O2** to see the call stack
+change. For how the `tailcall` transform composes with invoke/return, see
+the [transform context spec](/spec/program/context/transform) and the
+[tail-call optimization walkthrough](/docs/core-schemas/programs/tracing).
+
+
diff --git a/packages/web/docs/getting-started/_category_.json b/packages/web/docs/getting-started/_category_.json
index fd94d1fcf..eb95172a4 100644
--- a/packages/web/docs/getting-started/_category_.json
+++ b/packages/web/docs/getting-started/_category_.json
@@ -1,4 +1,4 @@
{
"label": "Getting started",
- "position": 2
+ "position": 3
}
diff --git a/packages/web/docs/getting-started/for-compiler-authors.mdx b/packages/web/docs/getting-started/for-compiler-authors.mdx
index 4a97a6300..2d0cc83ae 100644
--- a/packages/web/docs/getting-started/for-compiler-authors.mdx
+++ b/packages/web/docs/getting-started/for-compiler-authors.mdx
@@ -158,5 +158,5 @@ Most compilers can add **ethdebug/format** support incrementally:
- **[Pointers](/docs/core-schemas/pointers)** — Full documentation on pointer definitions
- **[Programs](/docs/core-schemas/programs)** — Full documentation on program annotations
- **[Info schema](/docs/core-schemas/info)** — Full documentation on bundling debug data
-- **[BUG Playground](/docs/examples/bug-playground)** — See a working
+- **[BUG Playground](/docs/explore/bug-playground)** — See a working
compiler that emits **ethdebug/format**
diff --git a/packages/web/docs/getting-started/index.mdx b/packages/web/docs/getting-started/index.mdx
index 14ecd87e6..abafba550 100644
--- a/packages/web/docs/getting-started/index.mdx
+++ b/packages/web/docs/getting-started/index.mdx
@@ -67,6 +67,6 @@ started based on what you're building.
- **[Concepts](/docs/concepts)** — Understand the mental models behind the
format
-- **[Examples](/docs/examples)** — See the format in action with interactive
+- **[Explore](/docs/explore)** — See the format in action with interactive
demos
- **[Specification](/spec/overview)** — Read the formal schema definitions
diff --git a/packages/web/docs/implementation-guides/_category_.json b/packages/web/docs/implementation-guides/_category_.json
index d4987100c..72272d4a1 100644
--- a/packages/web/docs/implementation-guides/_category_.json
+++ b/packages/web/docs/implementation-guides/_category_.json
@@ -1,4 +1,4 @@
{
"label": "Implementation guides",
- "position": 5
+ "position": 6
}
diff --git a/packages/web/docs/implementation-guides/building-a-trace-viewer.mdx b/packages/web/docs/implementation-guides/building-a-trace-viewer.mdx
new file mode 100644
index 000000000..c9ef15b7e
--- /dev/null
+++ b/packages/web/docs/implementation-guides/building-a-trace-viewer.mdx
@@ -0,0 +1,69 @@
+---
+sidebar_position: 3
+---
+
+# Building a trace viewer
+
+This guide walks through the pieces you need to turn **ethdebug/format**
+data and an execution trace into a working trace viewer. For the conceptual
+reference on how trace data maps to the format, see
+[Tracing execution](/docs/core-schemas/programs/tracing); to see a finished
+viewer in action, try the
+[Trace playground](/docs/explore/trace-playground).
+
+The key components for trace integration:
+
+## 1. Trace source
+
+Get transaction traces from:
+
+- JSON-RPC `debug_traceTransaction`
+- Local simulation (Ganache, Anvil, Hardhat)
+- Historical archive nodes
+
+## 2. Program loader
+
+Load compiled program data containing:
+
+- Instruction list with contexts
+- Source materials
+- Type definitions
+
+## 3. Pointer resolver
+
+Use `@ethdebug/pointers` to resolve variable locations:
+
+```typescript
+import { dereference } from "@ethdebug/pointers";
+
+// For each variable in scope
+const cursor = await dereference(variable.pointer, { state: machineState });
+const view = await cursor.view(machineState);
+const value = await view.read(view.regions[0]);
+```
+
+## 4. Type decoder
+
+Interpret raw bytes according to type:
+
+```typescript
+function decodeValue(bytes: Data, type: Type): string {
+ switch (type.kind) {
+ case "uint":
+ return bytes.asUint().toString();
+ case "bool":
+ return bytes.asUint() !== 0n ? "true" : "false";
+ case "address":
+ return "0x" + bytes.toHex().slice(-40);
+ // ... other types
+ }
+}
+```
+
+## Learn more
+
+- [Tracing execution](/docs/core-schemas/programs/tracing) for how trace
+ data maps to the format
+- [Pointers](/docs/core-schemas/pointers) for resolving variable locations
+- [Trace playground](/docs/explore/trace-playground) for a working viewer
+ you can step through
diff --git a/packages/web/docs/implementation-guides/compiler/case-study-bug.mdx b/packages/web/docs/implementation-guides/compiler/case-study-bug.mdx
index caa32751a..9082f31fa 100644
--- a/packages/web/docs/implementation-guides/compiler/case-study-bug.mdx
+++ b/packages/web/docs/implementation-guides/compiler/case-study-bug.mdx
@@ -12,7 +12,7 @@ integration. This case study explains how BUG implements debug information
generation, providing a reference for other compiler authors.
:::tip[Try it yourself]
-See the [BUG Playground](/docs/examples/bug-playground) to experiment with
+See the [BUG Playground](/docs/explore/bug-playground) to experiment with
BUG and see its debug output.
:::
@@ -366,6 +366,6 @@ Areas for improvement in BUG's debug support:
## Resources
-- [BUG Playground](/docs/examples/bug-playground) — Interactive compiler demo
+- [BUG Playground](/docs/explore/bug-playground) — Interactive compiler demo
- [BUG Source Code](https://github.com/ethdebug/format/tree/main/packages/bugc) — Implementation reference
- [ethdebug/format Specification](/spec/overview) — Format reference
diff --git a/packages/web/docs/implementation-guides/compiler/index.mdx b/packages/web/docs/implementation-guides/compiler/index.mdx
index 8c4e5d028..ce33ea5fb 100644
--- a/packages/web/docs/implementation-guides/compiler/index.mdx
+++ b/packages/web/docs/implementation-guides/compiler/index.mdx
@@ -54,5 +54,5 @@ demonstrates the full integration:
- **[Case Study: BUG](./case-study-bug)** — Walkthrough of how the
BUG compiler generates types, pointers, and program annotations
-- **[BUG Playground](/docs/examples/bug-playground)** — Interactive
+- **[BUG Playground](/docs/explore/bug-playground)** — Interactive
demo where you can compile BUG code and inspect the debug output
diff --git a/packages/web/docs/overview.mdx b/packages/web/docs/overview.mdx
index 74058f0d9..374c71599 100644
--- a/packages/web/docs/overview.mdx
+++ b/packages/web/docs/overview.mdx
@@ -79,7 +79,7 @@ each compiler-debugger pair requires custom integration work.
Or explore the [concepts](/docs/concepts) to understand the format's
-design, browse [examples](/docs/examples) to see it in action, or
+design, [explore](/docs/explore) interactive demos to see it in action, or
dive into the [specification](/spec/overview) for formal definitions.
To understand the motivation behind this project, see the