Render an ASCII memory map using the Python script at ${CLAUDE_SKILL_DIR}/scripts/render_ascii_memory_map.py.
Author the map as JSON (the data), choose presentation flags (the rendering), run the script, and paste its output into a fenced code block. Let the script do all box drawing, alignment, address formatting, and height scaling — do not hand-draw memory maps.
Positioning rules (the text-placement taxonomy)
Every piece of text has exactly one home, decided by what it refers to:
| Class |
What it is |
Where it goes |
| Address |
A hex/decimal boundary coordinate |
Left gutter, on the divider that opens the block (its low-address edge) |
| Label |
The block's short name |
Inside the box, first body row |
| Description |
Optional elaboration (extent, contents) |
Inside the box, further body rows |
| Comment / note |
Extrinsic annotation — register writes, bit patterns, caveats, consequences |
Right of the box, anchored to the first body row with an arrow; continuations align beneath, no arrow |
Decisive test: text that names or describes the contents of a span is intrinsic → goes inside (label/description); text that says something about the span (a value, a constraint, a cross-reference) is extrinsic → goes outside (comment). A bare boundary coordinate goes in the gutter. Divider and border lines carry only the address — never a label or a comment. The renderer enforces all of this.
Instructions
Construct a JSON object describing the memory map (schema below). Store addresses as numeric/hex strings — the renderer formats them for display, so the same data can be shown as $0400, 0x0400, 0400h, 0040:0000, or 01024 by changing one flag.
Write the JSON to a temporary file in the current project's working directory (e.g. memmap.json).
Run the renderer (use python3 on macOS/Linux, python on Windows):
python3 "${CLAUDE_SKILL_DIR}/scripts/render_ascii_memory_map.py" memmap.json [flags]
Defaults can also be set inside the JSON in a "render": { ... } object; CLI flags override the JSON, which overrides the built-in defaults.
Copy the output into the target markdown inside a fenced code block.
Delete the temporary JSON file.
JSON schema
Top-level keys:
| Key |
Type |
Meaning |
title |
string |
Optional heading printed above the map |
render |
object |
Optional default values for any flag below (underscore_case keys, e.g. "address_format": "c") |
blocks |
array |
The memory regions (required) |
Each block:
| Field |
Type |
Meaning |
start |
int or string |
Start address (required). Strings accept $XXXX, 0xXXXX, XXXXh, SSSS:OOOO, or a bare numeral (hex by default) |
end |
int or string |
Inclusive end address (last byte). Optional — if omitted, inferred from the next block's start |
size |
int or string |
Alternative to end; accepts 2K, 0x800, 4096, 64KB |
label |
string |
The region name (inside the box) |
description |
string or array |
Extra inside-the-box line(s) |
comments |
string or array |
Outside-the-box note(s); the first is arrow-anchored, the rest align beneath |
rows |
int |
Force this block's body height (overrides scaling) |
Parameters
| Flag |
Default |
Purpose |
--address-format |
commodore |
commodore ($0400), c (0x0400), intel (0400h), plain (0400), segmented (0040:0000), decimal, decimal-padded |
--address-width |
auto |
Digit count (4 = 16-bit, 8 = 32-bit…); auto-sized from the largest address if omitted |
--input-radix |
hex |
How to read bare numeric strings in the JSON (hex / decimal); integers are always decimal |
--hex-case |
upper |
upper / lower hex digits |
--origin |
top |
top = low address at top (addresses increase downward); bottom = low address at bottom |
--scale |
on |
on / off — size-proportional block heights |
--scale-mode |
log |
log / linear / sqrt taper |
--min-rows / --max-rows |
1 / 6 |
Clamp for scaled block heights |
--style |
unicode |
unicode / heavy / double / ascii (`+ - |
--width |
auto |
Interior box width: auto (longest label) or an integer |
--comment-arrow |
◄─ |
Outside-note connector (<- in ascii style) |
--no-comments |
off |
Suppress all outside notes |
--show-size |
off |
Append each region's computed size inside its box |
--show-end |
off |
Show the end address on the closing boundary |
--show-gaps |
off |
Draw unmapped ranges between blocks as their own gap blocks |
--gap-label |
· · · |
Label used for auto-generated gap blocks |
--paragraph |
16 |
Bytes per segment for segmented addresses |
--title |
— |
Override the JSON title |
Height scaling
With --scale on (default), a block's body height is max( rows_needed_for_text, clamp( round(min_rows + f(size)·(max_rows−min_rows)), min_rows, max_rows ) ), where f is a log/linear/sqrt normalisation of the block's size between the smallest and largest sized blocks. The log taper means one very large region is only a few rows taller than its neighbours — a visual cue to relative size, not a to-scale drawing. Tiny blocks stay readable at min_rows; text always fits regardless of scaling. Sizes are taken from end/size, or inferred from the next block's start.
Example
JSON (memmap.json):
{
"title": "C64 — VIC bank 0 ($0000-$3FFF)",
"blocks": [
{ "start": "$0000", "end": "$03FF", "label": "zero page / stack / system",
"comments": ["$0001 CHAREN: bit 2 = 0 → char ROM at $D000"] },
{ "start": "$0400", "end": "$07FF", "label": "screen RAM (video matrix)",
"comments": ["matrix base; $D018 bits 4-7 = %0001"] },
{ "start": "$3800", "end": "$3FFF", "label": "Westminster charset (2 KB)",
"description": ["ROM copy + overlaid glyphs"],
"comments": ["char base; $D018 bits 1-3 = %111 → $D018 = $1E"] }
]
}
Command:
python3 "${CLAUDE_SKILL_DIR}/scripts/render_ascii_memory_map.py" memmap.json --scale off
Output:
C64 — VIC bank 0 ($0000-$3FFF)
$0000 ┌────────────────────────────┐
│ zero page / stack / system │ ◄─ $0001 CHAREN: bit 2 = 0 → char ROM at $D000
$0400 ├────────────────────────────┤
│ screen RAM (video matrix) │ ◄─ matrix base; $D018 bits 4-7 = %0001
$3800 ├────────────────────────────┤
│ Westminster charset (2 KB) │ ◄─ char base; $D018 bits 1-3 = %111 → $D018 = $1E
│ ROM copy + overlaid glyphs │
└────────────────────────────┘
A ready-to-run example is bundled at ${CLAUDE_SKILL_DIR}/examples/c64-bank0.json.
User argument
$ARGUMENTS
1---2name: ascii-memory-map3description: Render an ASCII box-drawing memory map (an address-space layout diagram) from a JSON description. Use whenever a response needs a memory map, memory layout, address map, or address-space diagram for ANY architecture (Commodore VIC-20/C64, x86 real-mode/segmented, ARM/MCU flash+SRAM, flat 32/64-bit, etc.). Separates the map's DATA from its RENDERING and offloads all box drawing, column alignment, address formatting, and size-proportional block heights to a deterministic Python renderer, so the model only authors compact JSON.4---56Render an ASCII memory map using the Python script at `${CLAUDE_SKILL_DIR}/scripts/render_ascii_memory_map.py`.78Author the map as JSON (the *data*), choose presentation flags (the *rendering*), run the script, and paste its output into a fenced code block. Let the script do all box drawing, alignment, address formatting, and height scaling — do not hand-draw memory maps.910## Positioning rules (the text-placement taxonomy)1112Every piece of text has exactly one home, decided by what it refers to:1314| Class | What it is | Where it goes |15|-------|------------|---------------|16| **Address** | A hex/decimal boundary coordinate | Left gutter, on the divider that *opens* the block (its low-address edge) |17| **Label** | The block's short name | Inside the box, first body row |18| **Description** | Optional elaboration (extent, contents) | Inside the box, further body rows |19| **Comment / note** | Extrinsic annotation — register writes, bit patterns, caveats, consequences | Right of the box, anchored to the first body row with an arrow; continuations align beneath, no arrow |2021Decisive test: text that **names or describes the contents** of a span is intrinsic → goes **inside** (label/description); text that **says something about** the span (a value, a constraint, a cross-reference) is extrinsic → goes **outside** (comment). A bare boundary coordinate goes in the **gutter**. Divider and border lines carry only the address — never a label or a comment. The renderer enforces all of this.2223## Instructions24251. Construct a JSON object describing the memory map (schema below). Store addresses as numeric/hex strings — the renderer formats them for display, so the same data can be shown as `$0400`, `0x0400`, `0400h`, `0040:0000`, or `01024` by changing one flag.26272. Write the JSON to a temporary file in the current project's working directory (e.g. `memmap.json`).28293. Run the renderer (use `python3` on macOS/Linux, `python` on Windows):30 ```31 python3 "${CLAUDE_SKILL_DIR}/scripts/render_ascii_memory_map.py" memmap.json [flags]32 ```33 Defaults can also be set inside the JSON in a `"render": { ... }` object; CLI flags override the JSON, which overrides the built-in defaults.34354. Copy the output into the target markdown inside a fenced code block.36375. Delete the temporary JSON file.3839## JSON schema4041Top-level keys:4243| Key | Type | Meaning |44|-----|------|---------|45| `title` | string | Optional heading printed above the map |46| `render` | object | Optional default values for any flag below (underscore_case keys, e.g. `"address_format": "c"`) |47| `blocks` | array | The memory regions (required) |4849Each block:5051| Field | Type | Meaning |52|-------|------|---------|53| `start` | int or string | Start address (required). Strings accept `$XXXX`, `0xXXXX`, `XXXXh`, `SSSS:OOOO`, or a bare numeral (hex by default) |54| `end` | int or string | Inclusive end address (last byte). Optional — if omitted, inferred from the next block's `start` |55| `size` | int or string | Alternative to `end`; accepts `2K`, `0x800`, `4096`, `64KB` |56| `label` | string | The region name (inside the box) |57| `description` | string or array | Extra inside-the-box line(s) |58| `comments` | string or array | Outside-the-box note(s); the first is arrow-anchored, the rest align beneath |59| `rows` | int | Force this block's body height (overrides scaling) |6061## Parameters6263| Flag | Default | Purpose |64|------|---------|---------|65| `--address-format` | `commodore` | `commodore` (`$0400`), `c` (`0x0400`), `intel` (`0400h`), `plain` (`0400`), `segmented` (`0040:0000`), `decimal`, `decimal-padded` |66| `--address-width` | auto | Digit count (4 = 16-bit, 8 = 32-bit…); auto-sized from the largest address if omitted |67| `--input-radix` | `hex` | How to read bare numeric *strings* in the JSON (`hex` / `decimal`); integers are always decimal |68| `--hex-case` | `upper` | `upper` / `lower` hex digits |69| `--origin` | `top` | `top` = low address at top (addresses increase downward); `bottom` = low address at bottom |70| `--scale` | `on` | `on` / `off` — size-proportional block heights |71| `--scale-mode` | `log` | `log` / `linear` / `sqrt` taper |72| `--min-rows` / `--max-rows` | `1` / `6` | Clamp for scaled block heights |73| `--style` | `unicode` | `unicode` / `heavy` / `double` / `ascii` (`+ - |` for non-Unicode terminals) |74| `--width` | `auto` | Interior box width: `auto` (longest label) or an integer |75| `--comment-arrow` | `◄─` | Outside-note connector (`<-` in `ascii` style) |76| `--no-comments` | off | Suppress all outside notes |77| `--show-size` | off | Append each region's computed size inside its box |78| `--show-end` | off | Show the end address on the closing boundary |79| `--show-gaps` | off | Draw unmapped ranges between blocks as their own gap blocks |80| `--gap-label` | `· · ·` | Label used for auto-generated gap blocks |81| `--paragraph` | `16` | Bytes per segment for `segmented` addresses |82| `--title` | — | Override the JSON `title` |8384## Height scaling8586With `--scale on` (default), a block's body height is `max( rows_needed_for_text, clamp( round(min_rows + f(size)·(max_rows−min_rows)), min_rows, max_rows ) )`, where `f` is a log/linear/sqrt normalisation of the block's size between the smallest and largest sized blocks. The log taper means one very large region is only a few rows taller than its neighbours — a visual cue to relative size, not a to-scale drawing. Tiny blocks stay readable at `min_rows`; text always fits regardless of scaling. Sizes are taken from `end`/`size`, or inferred from the next block's `start`.8788## Example8990JSON (`memmap.json`):9192```json93{94 "title": "C64 — VIC bank 0 ($0000-$3FFF)",95 "blocks": [96 { "start": "$0000", "end": "$03FF", "label": "zero page / stack / system",97 "comments": ["$0001 CHAREN: bit 2 = 0 → char ROM at $D000"] },98 { "start": "$0400", "end": "$07FF", "label": "screen RAM (video matrix)",99 "comments": ["matrix base; $D018 bits 4-7 = %0001"] },100 { "start": "$3800", "end": "$3FFF", "label": "Westminster charset (2 KB)",101 "description": ["ROM copy + overlaid glyphs"],102 "comments": ["char base; $D018 bits 1-3 = %111 → $D018 = $1E"] }103 ]104}105```106107Command:108109```110python3 "${CLAUDE_SKILL_DIR}/scripts/render_ascii_memory_map.py" memmap.json --scale off111```112113Output:114115```116C64 — VIC bank 0 ($0000-$3FFF)117118 $0000 ┌────────────────────────────┐119 │ zero page / stack / system │ ◄─ $0001 CHAREN: bit 2 = 0 → char ROM at $D000120 $0400 ├────────────────────────────┤121 │ screen RAM (video matrix) │ ◄─ matrix base; $D018 bits 4-7 = %0001122 $3800 ├────────────────────────────┤123 │ Westminster charset (2 KB) │ ◄─ char base; $D018 bits 1-3 = %111 → $D018 = $1E124 │ ROM copy + overlaid glyphs │125 └────────────────────────────┘126```127128A ready-to-run example is bundled at `${CLAUDE_SKILL_DIR}/examples/c64-bank0.json`.129130## User argument131132$ARGUMENTS