Sfoglia il codice sorgente

newLang: add Nova128 spec, backlog, and C128 platform probes

Nova128 is an object-oriented language with an on-machine C128 compiler.
This adds the spec, decisions, architecture, backlog, and story claims,
plus the grammar fixtures (E00-S02), disk-image builder (E07-S01), and
the x128-verified platform probes:

- E01-S01 minimal native PRG
- E01-S02 MMU/common-RAM probe. With bottom common on, a P0/P1 pointer
  aimed at bank 1 lands in bank 0 at the translated page. The verifier
  traces every MMU write in the probe window.
- E01-S03 KERNAL wrapper (kcall). CHROUT, drive status, and a
  device-not-present failure work from bank 0 and bank 1 mappings.
  A/X/Y/C/ST are preserved and the MMU state is restored exactly.
- E01-S04 40/80-column display and clock probe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
Daniel Covington 6 giorni fa
parent
commit
6555d89d99
79 ha cambiato i file con 5814 aggiunte e 0 eliminazioni
  1. +7
    -0
      newLang/.gitignore
  2. +109
    -0
      newLang/AGENTS.md
  3. +74
    -0
      newLang/README.md
  4. +19
    -0
      newLang/cfg/display-clock-c128.cfg
  5. +20
    -0
      newLang/cfg/kernal-probe-c128.cfg
  6. +19
    -0
      newLang/cfg/minimal-c128.cfg
  7. +20
    -0
      newLang/cfg/mmu-probe-c128.cfg
  8. +371
    -0
      newLang/docs/architecture.md
  9. +63
    -0
      newLang/docs/decisions.md
  10. +100
    -0
      newLang/docs/kernal-wrapper.md
  11. +380
    -0
      newLang/docs/language-spec.md
  12. +87
    -0
      newLang/docs/mmu-probe.md
  13. +96
    -0
      newLang/docs/platform-display-clock.md
  14. +19
    -0
      newLang/monitor/display-clock-trace.txt
  15. +9
    -0
      newLang/monitor/kernal-probe-trace.txt
  16. +28
    -0
      newLang/monitor/minimal-debug.txt
  17. +14
    -0
      newLang/monitor/minimal-trace.txt
  18. +12
    -0
      newLang/monitor/mmu-probe-debug.txt
  19. +18
    -0
      newLang/monitor/mmu-probe-trace.txt
  20. +145
    -0
      newLang/planning/agent-workflow.md
  21. +48
    -0
      newLang/planning/claims/E00-S01.md
  22. +53
    -0
      newLang/planning/claims/E00-S02.md
  23. +52
    -0
      newLang/planning/claims/E00-S03.md
  24. +64
    -0
      newLang/planning/claims/E01-S01.md
  25. +80
    -0
      newLang/planning/claims/E01-S02.md
  26. +74
    -0
      newLang/planning/claims/E01-S03.md
  27. +68
    -0
      newLang/planning/claims/E01-S04.md
  28. +71
    -0
      newLang/planning/claims/E07-S01.md
  29. +9
    -0
      newLang/planning/claims/README.md
  30. +670
    -0
      newLang/planning/epics-and-stories.md
  31. +74
    -0
      newLang/scripts/build-display-clock.ps1
  32. +74
    -0
      newLang/scripts/build-kernal-probe.ps1
  33. +64
    -0
      newLang/scripts/build-minimal.ps1
  34. +62
    -0
      newLang/scripts/build-mmu-probe.ps1
  35. +206
    -0
      newLang/scripts/prepare-disk.ps1
  36. +40
    -0
      newLang/scripts/run-minimal.ps1
  37. +27
    -0
      newLang/scripts/run-mmu-probe.ps1
  38. +174
    -0
      newLang/scripts/verify-display-clock.ps1
  39. +189
    -0
      newLang/scripts/verify-kernal-probe.ps1
  40. +111
    -0
      newLang/scripts/verify-minimal.ps1
  41. +188
    -0
      newLang/scripts/verify-mmu-probe.ps1
  42. +166
    -0
      newLang/src/platform/display-clock.s
  43. +507
    -0
      newLang/src/platform/kernal-probe.s
  44. +60
    -0
      newLang/src/platform/minimal.s
  45. +505
    -0
      newLang/src/platform/mmu-probe.s
  46. +122
    -0
      newLang/tests/disk/prepare-disk.tests.ps1
  47. +79
    -0
      newLang/tests/grammar/README.md
  48. +23
    -0
      newLang/tests/grammar/accepted/arrays.n8
  49. +50
    -0
      newLang/tests/grammar/accepted/decls.n8
  50. +34
    -0
      newLang/tests/grammar/accepted/exprs.n8
  51. +24
    -0
      newLang/tests/grammar/accepted/forward.n8
  52. +3
    -0
      newLang/tests/grammar/accepted/minimal.n8
  53. +19
    -0
      newLang/tests/grammar/accepted/nulls.n8
  54. +44
    -0
      newLang/tests/grammar/accepted/stmts.n8
  55. +4
    -0
      newLang/tests/grammar/rejected/bad_char.expect
  56. +4
    -0
      newLang/tests/grammar/rejected/bad_char.n8
  57. +3
    -0
      newLang/tests/grammar/rejected/bad_class.expect
  58. +5
    -0
      newLang/tests/grammar/rejected/bad_class.n8
  59. +3
    -0
      newLang/tests/grammar/rejected/bad_iface.expect
  60. +6
    -0
      newLang/tests/grammar/rejected/bad_iface.n8
  61. +3
    -0
      newLang/tests/grammar/rejected/bad_method.expect
  62. +8
    -0
      newLang/tests/grammar/rejected/bad_method.n8
  63. +3
    -0
      newLang/tests/grammar/rejected/dup_decl.expect
  64. +4
    -0
      newLang/tests/grammar/rejected/dup_decl.n8
  65. +3
    -0
      newLang/tests/grammar/rejected/iface_body.expect
  66. +8
    -0
      newLang/tests/grammar/rejected/iface_body.n8
  67. +3
    -0
      newLang/tests/grammar/rejected/line_cont.expect
  68. +5
    -0
      newLang/tests/grammar/rejected/line_cont.n8
  69. +3
    -0
      newLang/tests/grammar/rejected/miss_class.expect
  70. +5
    -0
      newLang/tests/grammar/rejected/miss_class.n8
  71. +3
    -0
      newLang/tests/grammar/rejected/miss_end.expect
  72. +2
    -0
      newLang/tests/grammar/rejected/miss_end.n8
  73. +3
    -0
      newLang/tests/grammar/rejected/miss_if.expect
  74. +4
    -0
      newLang/tests/grammar/rejected/miss_if.n8
  75. +3
    -0
      newLang/tests/grammar/rejected/miss_meth.expect
  76. +7
    -0
      newLang/tests/grammar/rejected/miss_meth.n8
  77. +3
    -0
      newLang/tests/grammar/rejected/multi_stmt.expect
  78. +3
    -0
      newLang/tests/grammar/rejected/multi_stmt.n8
  79. +72
    -0
      newLang/tests/grammar/validate.ps1

+ 7
- 0
newLang/.gitignore Vedi File

@@ -0,0 +1,7 @@
# Generated compiler, disk-image, screenshot, and test artifacts
build/

# Agent-local scratch files; durable story state belongs in planning/claims/
*.tmp
*.log


+ 109
- 0
newLang/AGENTS.md Vedi File

@@ -0,0 +1,109 @@
# AGENTS.md — Nova128 Project Rules

This file applies to every file below `newLang/`.

## Mission

Build an object-oriented language and an on-machine compiler for the Commodore
128. The compiler is written in documented 8502/6502 assembly, runs in C128
native mode, reads source written by the repository's existing Vim clone, and
emits native C128 PRG files.

`Nova128` and the `.n8` suffix are working names until the user approves them.

## Start Every Work Session Here

Read these files in order before changing implementation files:

1. `README.md`
2. `docs/decisions.md`
3. `docs/language-spec.md`
4. `docs/architecture.md`
5. `planning/epics-and-stories.md`
6. `planning/agent-workflow.md`

Also read the repository-root `AGENTS.md` and the relevant skills under
`../skills/`. Compiler work requires `../skills/compiler-design/SKILL.md`.
Emulator work also requires `../skills/vice-emulator/SKILL.md`.

## Authority and Change Control

The canonical order is:

1. The user's latest instruction.
2. `docs/decisions.md` for accepted and provisional product decisions.
3. `docs/language-spec.md` for source-language behavior.
4. `docs/architecture.md` for compiler/runtime implementation contracts.
5. `planning/epics-and-stories.md` for scope and acceptance criteria.

Do not silently change syntax, type rules, object layout, the interface ABI,
file formats, or memory ownership. Record a proposed change in
`docs/decisions.md` and obtain user approval when it changes visible behavior
or project scope.

## Story Ownership

- Work from a story ID in `planning/epics-and-stories.md`.
- Before implementation, create `planning/claims/<story-id>.md` from the
template in `planning/agent-workflow.md`.
- One agent owns one active story unless coordination is explicit.
- Check existing claims and `git status` before editing.
- Do not take a story whose dependencies are incomplete.
- Keep changes inside the story's listed file areas when possible.
- Never discard, reset, or overwrite another agent's uncommitted work.

## Definition of Done

A story is complete only when:

- Every acceptance criterion has evidence.
- Relevant automated tests pass.
- VICE behavior is verified when the story touches C128 execution.
- Documentation matches the implementation.
- Generated files are not confused with authoritative source files.
- The claim records files changed, commands run, results, and remaining risks.

## Platform Constraints

- Target C128 native mode on the 8502. C64 and Z80 targets are out of scope for
version 1.
- Use documented 6502/8502 instructions; do not depend on illegal opcodes.
- Use `ca65` and `ld65` for host assembly/linking.
- Test C128 programs with VICE `x128.exe`, not `x64sc.exe`.
- Use explicit MMU wrapper routines. Compiler modules must not perform ad hoc
bank switching.
- Keep the hardware stack for calls, interrupts, and short-lived internals.
Language frames and substantial compiler stacks belong in software-managed
memory.
- KERNAL entry must occur through wrappers that install a compatible memory
configuration and restore the compiler configuration afterward.
- Exact memory addresses remain provisional until the E01 platform probes pass.

## Editor and Disk Safety

- The current Vim clone is a C64-mode application. Version 1 integrates by
exchanging PETSCII SEQ files on a copied D81 image.
- Never run `x64sc` and `x128` against the same writable D81 simultaneously.
- Never modify `../vim/work.d81` or a reference disk image in place.
- Build and test scripts must make a working copy under `newLang/build/`.
- Human-readable host source is authoritative. PETSCII files and disk images
are generated artifacts unless a fixture explicitly says otherwise.

## Source and Output Contracts

- Nova128 source is PETSCII SEQ with `$0D` line endings.
- The supported editor profile is at most 80 bytes per logical line, about
18,999 source bytes, and 599 lines per file.
- Keep source base names at 12 characters or fewer so `.n8`, `.err`, and `.map`
variants fit Commodore DOS's 16-character filename limit.
- Compiler outputs are `.prg`, `.err`, and `.map` files as specified in the
architecture document.

## Repository Hygiene

- Put all new project work under `newLang/`.
- Do not edit `../vim/` merely to make the compiler easier to build.
- Do not commit generated `build/` contents unless a release story explicitly
requires a packaged artifact.
- Prefer small, reviewable ca65 modules and deterministic test fixtures.


+ 74
- 0
newLang/README.md Vedi File

@@ -0,0 +1,74 @@
# Nova128

Nova128 is the working name for an object-oriented programming language for the
Commodore 128. Its compiler will be written in 8502/6502 assembly, run on the
C128 in native mode, and generate native C128 PRG files.

This directory contains the project specification, agent-ready backlog, and the
implementation as it is built. The user approved continuation on 2026-11-07;
stories may be claimed in dependency order under `AGENTS.md`.

## Version 1 Product Boundary

- On-machine C128-native compiler written in ca65 assembly
- Statically typed, class-based language
- Classes, private/public fields and methods, constructors, and destructors
- Explicit `new` and `delete`
- Multiple interfaces per class
- Interface-typed variables and parameters
- Dynamic dispatch through interface method tables
- Procedural control flow and native 8502 code generation
- PETSCII SEQ source compatibility with the existing C64 Vim clone
- 40- or 80-column compiler output through KERNAL-compatible routines

Version 1 does not include class inheritance, garbage collection, exceptions,
generics, closures, overloading, floating point, a Z80 target, or a C64 target.

## Document Map

| File | Purpose |
|---|---|
| `AGENTS.md` | Mandatory operating rules for agents |
| `docs/decisions.md` | Accepted, provisional, deferred, and open decisions |
| `docs/language-spec.md` | Normative source-language specification |
| `docs/architecture.md` | Compiler, runtime, ABI, memory, and file architecture |
| `planning/epics-and-stories.md` | Work breakdown with dependencies and acceptance criteria |
| `planning/agent-workflow.md` | Claiming, handoff, verification, and completion process |

## Intended Workflow

```text
x64sc + Vim clone
|
| PETSCII SEQ source: PROGRAM.N8
v
copied development D81
|
| close x64sc before continuing
v
x128 + Nova128 compiler
|
+--> PROGRAM.PRG
+--> PROGRAM.ERR
+--> PROGRAM.MAP
```

The original `vim/work.d81` is an input template, never a writable project
disk. A preparation script will copy it into `newLang/build/` and inject the
compiler, runtime, examples, and fixtures.

## Approved Version 1 Defaults

The user approved continuation with these documented defaults:

- Working language name: `Nova128`
- Source suffix: `.n8`
- Compiler runs entirely on the C128
- Host tools only assemble/package/test the compiler itself
- Vim remains C64-mode for version 1
- Interfaces are required in version 1
- Class inheritance is deferred
- Memory management is explicit and deterministic

See `docs/decisions.md` for decision status and unresolved questions.


+ 19
- 0
newLang/cfg/display-clock-c128.cfg Vedi File

@@ -0,0 +1,19 @@
# E01-S04-only layout. This is not the final compiler memory map.
MEMORY {
LOADADDR: file = %O, start = $1BFF, size = $0002;
HEADER: file = %O, start = $1C01, size = $000C;
MAIN: file = %O, start = $1C0D, size = $0FF3;
}

SEGMENTS {
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
CODE: load = MAIN, type = ro;
RODATA: load = MAIN, type = ro;
DATA: load = MAIN, type = rw;
}

SYMBOLS {
__E01_S04_ONLY__: type = weak, value = 1;
}


+ 20
- 0
newLang/cfg/kernal-probe-c128.cfg Vedi File

@@ -0,0 +1,20 @@
# E01-S03-only layout. The complete image must remain inside the bottom 8K so
# it executes from bank-0 common RAM while the CPU selects bank 1.
MEMORY {
LOADADDR: file = %O, start = $1BFF, size = $0002;
HEADER: file = %O, start = $1C01, size = $000C;
COMMON: file = %O, start = $1C0D, size = $03F3;
}

SEGMENTS {
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
CODE: load = COMMON, type = ro;
RODATA: load = COMMON, type = ro;
DATA: load = COMMON, type = rw;
}

SYMBOLS {
__E01_S03_ONLY__: type = weak, value = 1;
}


+ 19
- 0
newLang/cfg/minimal-c128.cfg Vedi File

@@ -0,0 +1,19 @@
# E01-S01-only layout. This is not the final compiler memory map.
MEMORY {
LOADADDR: file = %O, start = $1BFF, size = $0002;
HEADER: file = %O, start = $1C01, size = $000C;
MAIN: file = %O, start = $1C0D, size = $0FF3;
}

SEGMENTS {
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
CODE: load = MAIN, type = ro;
RODATA: load = MAIN, type = ro;
DATA: load = MAIN, type = rw;
}

SYMBOLS {
__E01_S01_ONLY__: type = weak, value = 1;
}


+ 20
- 0
newLang/cfg/mmu-probe-c128.cfg Vedi File

@@ -0,0 +1,20 @@
# E01-S02-only layout. The complete image must remain inside the bottom 8K so
# it executes from bank-0 common RAM while the CPU selects bank 1.
MEMORY {
LOADADDR: file = %O, start = $1BFF, size = $0002;
HEADER: file = %O, start = $1C01, size = $000C;
COMMON: file = %O, start = $1C0D, size = $03F3;
}

SEGMENTS {
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
CODE: load = COMMON, type = ro;
RODATA: load = COMMON, type = ro;
DATA: load = COMMON, type = rw;
}

SYMBOLS {
__E01_S02_ONLY__: type = weak, value = 1;
}


+ 371
- 0
newLang/docs/architecture.md Vedi File

@@ -0,0 +1,371 @@
---
type: compiler-runtime-architecture
source-path: expert design plan, repository Vim notes, C128 reference architecture
ingested-at: 2026-11-07
version: 0.1-draft
---

# Nova128 Compiler and Runtime Architecture

Status: approved version 1 baseline. Exact addresses are deliberately absent
until the platform-probe epic validates them in VICE and, when available, on
hardware.

## 1. System Boundary

The deliverable compiler is an 8502 program that runs on a Commodore 128 in
native mode. Host-side tools may:

- assemble and link the compiler with `ca65` and `ld65`;
- copy files into or out of D81 images with `c1541`;
- launch and control VICE;
- compare deterministic test outputs.

Host tools must not perform Nova128 parsing, type checking, or code generation
in the release workflow. The C128 compiler performs those functions.

Generated version 1 programs target C128 native 8502 mode. They do not target
C64 mode or the Z80.

## 2. Toolchain and Artifacts

### 2.1 Authoritative source

- Compiler/runtime assembly: ASCII ca65 source under `src/`
- Linker layouts: ld65 configuration under `cfg/`
- Language examples: readable ASCII mirrors under `examples/`
- PETSCII/D81 forms: generated under `build/`

### 2.2 Compiler artifacts

| Artifact | Purpose |
|---|---|
| `N128C.PRG` | On-machine compiler executable |
| `NAME.N8` | PETSCII SEQ input source |
| `NAME.PRG` | Generated application |
| `NAME.ERR` | Complete stable-code diagnostics |
| `NAME.MAP` | Symbols, addresses, class layouts, interface slots, and section sizes |

Output replacement must be explicit. The compiler must not silently overwrite
an existing PRG after a failed compilation.

## 3. Repository Module Plan

```text
src/
compiler/
main.s command loop and phase orchestration
mmu.s all MMU transitions and bank-safe copies
diskio.s KERNAL file and command-channel wrappers
source.s buffered source stream and positions
lexer.s PETSCII normalization and tokenization
parser.s recursive-descent declarations/statements
expr.s precedence expression parser
names.s interning and identifier comparison
symbols.s scopes, symbols, and lookups
types.s type representation and compatibility
classes.s field/method layout and access checks
interfaces.s conformance and interface slot assignment
sizing.s dry-run code sizing and address assignment
codegen.s documented 8502 instruction emission
output.s PRG, ERR, and MAP writers
diagnostics.s stable error catalog and recovery
runtime/
startup.s generated-program entry and initialization
frames.s software argument/local-frame support
objects.s object construction and destruction
interfaces.s interface conversion and indirect call trampoline
heap.s deterministic allocator and free list
arithmetic.s non-inline arithmetic helpers
print.s scalar and literal output
faults.s null, bounds, divide, stack, and heap failures
common/
c128.inc hardware and KERNAL symbols
formats.inc compiler record and output constants
tokens.inc token numbers
errors.inc stable diagnostic numbers
```

Files are introduced by stories; this tree is a target, not permission to add
empty placeholders.

## 4. C128 Memory Strategy

The design uses the two 64K RAM banks and a verified common-RAM window.

### 4.1 Compiler bank roles

- **Bank 0:** resident compiler code, phase controller, lexer/parser state,
disk/KERNAL wrappers, diagnostics, and small buffers.
- **Bank 1:** interned names, symbols, class records, interface records, method
signatures, type records, and pass workspaces.
- **Common RAM:** MMU transition code, hardware stack, software-stack metadata,
disk transfer buffer, and state required while either bank is selected.

No module other than `mmu.s` directly changes the MMU. Banked data is accessed
through handles or explicit copy routines; ordinary pointers must not escape
the bank in which they are valid.

### 4.2 Memory-map freeze gate

Story E01-S02 must prove:

- chosen MMU configurations;
- common-memory size and placement;
- page-zero and page-one behavior;
- safe execution of the bank-switch trampoline;
- KERNAL entry and return mappings;
- interrupt behavior while a non-default bank is active.

Only then may linker addresses and zero-page allocations become normative.

E01-S02 evidence is in `docs/mmu-probe.md`. In VICE x128 3.10, distinct banks,
8K bottom+top common RAM, bank-switched `JSR`/`RTS`, and full MMU restoration
pass. Page pointers aimed at bank 1 while bottom common is active resolve to
bank 0 at the translated page, so Nova128 keeps P0/P1 in bank 0. Non-default
mappings run with IRQs masked; an IRQ path under bank 1 is not yet proven.

E01-S03 evidence is in `docs/kernal-wrapper.md`. A common-RAM wrapper enters
the KERNAL in BASIC's mapping and restores the exact compiler MMU state. From
bank 0 and bank 1 compiler mappings, CHROUT, a drive-8 status read, and a
device-not-present failure all work, with A/X/Y/C/ST preserved and no channels
left open.

### 4.3 Display and clock

Compiler text uses KERNAL-compatible character output on the display already
selected by the user; it does not force 80-column mode. E01-S04 verified the
editor's `$00D7` mode flag in x128: 40-column uses a deterministic 1 MHz
fallback, while 80-column may set `$D030` bit 0 for 2 MHz KERNAL output. The
speed bit is cleared before returning to BASIC, and any uncertain mode falls
back to 1 MHz. Direct VDC screen memory is display-only and is not compiler
semantic storage. See `docs/platform-display-clock.md` for evidence.

## 5. Compiler Passes

The compiler reparses a buffered/streamed source rather than retaining a large
pointer-rich abstract syntax tree.

### Pass 1 — declarations and layout

- tokenize the complete source;
- intern names;
- collect interfaces, classes, fields, methods, parameters, and program data;
- validate duplicate declarations and top-level structure;
- assign field offsets;
- assign a stable slot order within every interface;
- build class-to-interface conformance records.

### Pass 2 — semantics and sizing

- parse method/program bodies;
- resolve names and access control;
- check types, returns, and interface conversions;
- perform definite-local-initialization checks that fit the compiler budget;
- dry-run code generation to determine method sizes and addresses;
- calculate literal, descriptor, interface-table, and runtime sections.

### Pass 3 — emission

- repeat semantic parsing deterministically;
- emit documented 8502 instructions;
- emit literals, class descriptors, and interface method tables;
- resolve branches and addresses against Pass 2 assignments;
- append/link required runtime sections;
- write PRG and MAP output;
- delete or invalidate partial output on failure.

Repeated passes trade disk/source traversal time for bounded memory. The source
stream should be rewindable without reopening unsafe shared emulator state.

## 6. Compiler Data Records

Final byte layouts are story outputs, but records require these logical fields:

- **Interned name:** hash, length, original spelling location, normalized bytes
- **Symbol:** name ID, kind, type ID, scope ID, flags, value/address
- **Type:** kind, size, referenced class/interface/element, array length
- **Class:** name ID, instance size, field range, method range, interface range
- **Field:** owner class, name ID, type ID, access, byte offset
- **Method:** owner, name ID, signature, access, code address, code size
- **Interface:** name ID, ordered signature range
- **Implementation:** class ID, interface ID, emitted table address
- **Diagnostic:** code, file position, arguments

Use compact fixed-width records where practical. Every table has an explicit
capacity and produces a resource diagnostic rather than corrupting adjacent
memory.

## 7. Generated Program ABI

### 7.1 Scalar representation

- Multi-byte values are little-endian.
- `boolean`, `byte`, and `char` occupy one byte.
- `integer`, `word`, concrete references, and addresses occupy two bytes.
- Interface references occupy four bytes.

### 7.2 Return convention

- One-byte result: accumulator A
- Two-byte result: A is low byte, X is high byte
- Four-byte interface result: runtime-defined result area referenced through a
reserved ABI pointer; exact zero-page location is frozen after E01
- No result: A and X are caller-clobbered

The final register-clobber table is part of story E02-S02.

### 7.3 Frames and calls

- The hardware stack carries return addresses and small runtime temporaries.
- Arguments, locals, and expression spills use a software-managed stack/frame.
- `self` is held in a reserved two-byte ABI pointer during an instance call.
- Parameters are laid out in declaration order using their ABI sizes.
- Concrete method calls are statically resolved to a direct `JSR`.
- The runtime checks stack bounds before frame growth in debug builds and at
defined safety points in release builds.

### 7.4 Object layout

Every heap object begins with a two-byte class-descriptor pointer, followed by
fields in declaration order with no hidden alignment padding:

```text
+0 class descriptor pointer (2)
+2 first declared field
... remaining fields
```

The class descriptor contains the instance size, optional destructor address,
and a directory of implemented interfaces. Its final packed format is frozen
by E05 and E06 stories.

### 7.5 Interface representation and dispatch

An interface value is a fat reference:

```text
+0 object pointer (2)
+2 interface method-table pointer (2)
```

Each interface defines a slot order from its source declaration. For every
implemented interface, a class emits a table of two-byte method addresses in
that order.

Converting a concrete reference to an interface reference:

1. copies the object pointer;
2. uses compile-time-known class/interface metadata to select the class's
implementation table;
3. stores the table pointer in the interface value.

An interface call:

1. checks the object pointer for null;
2. installs the object pointer as `self`;
3. loads the two-byte target from the known interface slot;
4. invokes the runtime's indirect-call trampoline.

The 8502 has no `JSR (address)` instruction, so the trampoline must establish a
synthetic return path safely. It is tested independently before interface code
generation relies on it.

`delete` through an interface uses the object pointer and its class descriptor,
not the interface table, to find the optional destructor and object size.

## 8. Heap and Ownership

- The generated-program heap initially resides in the same RAM bank as program
data; banked heaps are deferred.
- `new` requests the class instance size, zeroes the allocation, stores the
descriptor pointer, and invokes `init`.
- `delete` invokes `destroy` at most once for that call and returns the entire
block to a coalescing free list.
- Allocation failure and corrupt/double-free detection use runtime fault codes.
- Reference copying does not change an ownership count.
- Aliases can outlive a deletion; that is a program error. Debug poisoning may
help diagnose it but is not a correctness mechanism.

## 9. KERNAL and Disk I/O

The compiler uses documented KERNAL entry points through `diskio.s` wrappers.
Each wrapper:

1. records compiler mapping state;
2. installs the verified KERNAL-compatible mapping;
3. performs the operation and reads status;
4. restores compiler mapping state;
5. reports errors without leaving files or channels open.

The command channel must be opened before source/output files when status is
needed and closed last, matching the proven editor workflow.

## 10. Vim Integration

The existing Vim clone is treated as an external producer/consumer of SEQ
files. Version 1 does not link the editor and compiler in memory.

The development image workflow is strictly sequential:

1. copy `../vim/work.d81` to `build/newlang-dev.d81`;
2. inject `N128C.PRG`, examples, and fixtures;
3. launch `x64sc` and autostart `VIM` for editing;
4. close the process and wait for image flush;
5. launch `x128` and autostart `N128C.PRG` for compilation;
6. close the compiler session before inspecting or modifying the image.

Scripts must refuse obvious concurrent use where detectable. Tests create a
fresh image per run.

## 11. Diagnostics and Faults

Compile-time diagnostics use stable IDs such as `E014`. Runtime faults use a
separate stable namespace such as `R003`. Numeric assignments live in
`src/common/errors.inc` once story E02-S03 freezes the catalog.

Compiler errors include source name, line, column, and compact message. Runtime
faults always include a stable fault code and the best available program address
that can be resolved through the MAP file. Rich stack traces are deferred.

## 12. Verification Architecture

Testing is layered:

1. Host checks for deterministic converters, image preparation, and byte-level
expected files.
2. Compiler fixtures for lexer, parser, symbols, types, class layout, interface
conformance, and diagnostics.
3. Golden PRG bytes and disassembly for small generated routines.
4. End-to-end x128 runs that write a result signature to a fixed test mailbox
chosen during E01.
5. Monitor scripts with MMU, stack, heap, and dispatch break/watch points.
6. Editor round-trip: Vim saves source, C128 compiler consumes it without host
conversion, and Vim reopens unchanged source.
7. Physical C128 smoke testing when hardware is available.

Each emulator test has a cycle/time limit and captures enough output to explain
failure without relying only on a screenshot.

## 13. Principal Risks

- Incorrect MMU assumptions corrupt compiler state or KERNAL execution.
- Interface fat references increase local/array memory use.
- The indirect-call trampoline can corrupt the hardware stack if designed or
tested casually.
- Repeated parsing can be slow on real disks.
- Cross-bank raw pointers can create intermittent corruption.
- Feature growth can exceed the on-machine compiler's memory budget.
- Two VICE processes can overwrite the same D81 image.

The story order makes each high-risk mechanism prove itself before downstream
features depend on it.

## Open Questions

- Exact bank layouts, zero-page reservations, and common-RAM window
- Final interface-result convention
- Compiler table capacities and their user-visible resource limits
- Whether to cache the complete source in banked RAM when it fits
- Physical-hardware acceptance requirements


+ 63
- 0
newLang/docs/decisions.md Vedi File

@@ -0,0 +1,63 @@
---
type: project-decisions
source-path: user conversation and repository inspection
ingested-at: 2026-11-07
---

# Project Decisions

This file prevents later agents from silently changing the approved product.
`Accepted` means the user explicitly requested, confirmed, or approved the
documented default. `Proposed` means a later change still needs approval.

## Accepted

| ID | Decision | Consequence |
|---|---|---|
| D001 | Create an object-oriented language for the Commodore 128. | C128 behavior and limits define the platform contract. |
| D002 | Write the compiler in assembly. | Compiler implementation uses documented 8502/6502 instructions and ca65 source. |
| D003 | Source creation must work with the repository's new Vim clone. | The compiler accepts the editor's PETSCII SEQ output directly. |
| D004 | Put project programs and supporting material under `newLang/`. | Existing `vim/` and reference assets remain external inputs. |
| D005 | Plan and obtain approval before implementation. | The user approved continuation on 2026-11-07; implementation stories may now be claimed in dependency order. |
| D006 | Support interfaces. | Interfaces and dynamic interface dispatch are version 1 work, not an optional extension. |
| D007 | Create a specification and epics/stories suitable for other agents. | Work is divided into claimable, testable stories with durable context. |
| D100 | Use `Nova128` as the language name and `.n8` as the source suffix. | Short, recognizable, and compatible with 16-character disk filenames. |
| D101 | Run the compiler in C128 native 8502 mode. | Gives access to both RAM banks, the MMU, 80-column facilities, and 2 MHz operation where safe. |
| D102 | Use host `ca65`/`ld65` only to build the on-machine compiler. | The resulting compiler still runs entirely on the C128. |
| D103 | Keep the existing Vim binary in C64 mode for version 1. | A native C128 editor port is a separate large project. |
| D104 | Exchange source through a copied D81 image. | It works with the current editor without inventing an editor API. |
| D105 | Use explicit `new`/`delete` and a deterministic free-list heap. | Avoids the size and timing cost of garbage collection. |
| D106 | Permit multiple interfaces but no class inheritance in version 1. | Delivers polymorphism while limiting layout and dispatch complexity. |
| D107 | Use statically dispatched concrete method calls and dynamically dispatched interface calls. | Keeps ordinary calls fast while preserving interface polymorphism. |
| D108 | Represent interface values as a four-byte pair: object pointer plus interface-table pointer. | Makes interface invocation constant-time without searching class metadata on every call. |
| D109 | Compile one source file into one PRG in version 1. | Separate compilation and object linking can follow a proven compiler. |
| D110 | Use KERNAL-compatible output so the compiler can operate on 40- or 80-column displays. | Avoids coupling the first compiler UI directly to one video chip. |
| D111 | Physical C128 testing is P2 validation, not a version 1 release blocker when hardware is unavailable. | VICE x128 remains the mandatory automated acceptance environment. |
| D112 | Compiler diagnostics use the active C128 display and do not force 80-column mode. | The compiler remains usable with either display; safe clock acceleration is opportunistic. |
| D113 | Version 1 integer overflow always wraps in the destination representation. | Debug overflow trapping is deferred, keeping semantics identical between builds. |
| D114 | Version 1 has no interface-to-class down-cast operation. | Programs express polymorphism through interfaces without runtime type reflection. |
| D115 | Character literals use single quotes and contain exactly one PETSCII character or supported escape. | The `char` type has a direct, unambiguous literal form while `//` remains the comment syntax. |
| D116 | Runtime faults report a stable code and the best available map-resolvable program address. | Rich stack traces are deferred; faults remain diagnosable within the runtime budget. |

## Proposed

No proposed product changes are currently pending.

## Deferred

- Native C128/80-column Vim port
- Vim `:make` and error-navigation commands
- Class inheritance and virtual class methods
- Interface inheritance and default methods
- Separate compilation and relocatable Nova128 object files
- Garbage collection or reference counting
- Exceptions, generics, closures, reflection, and method overloading
- Floating point
- C64 and Z80 code-generation targets
- Bank 1 heaps for generated programs

## Open Questions

1. What exact compiler and generated-program memory maps pass the E01 probes?
2. What compiler table capacities fit after the verified memory map is frozen?


+ 100
- 0
newLang/docs/kernal-wrapper.md Vedi File

@@ -0,0 +1,100 @@
# C128 KERNAL Mapping Wrapper Probe

Story: E01-S03
Status: observed in VICE x128 3.10 with a true-emulation 1541; not the final
compiler map

## Wrapper Contract (`kcall`)

```text
in: kc_target = KERNAL entry address; A/X/Y = routine arguments
out: A/X/Y and N/V/Z/C exactly as the routine returned them
I flag = the caller's I flag
CR, RCR, P0L/H, P1L/H = their values at the call
```

Sequence (`src/platform/kernal-probe.s`):

1. `PHP`/`SEI`, save A and the caller's P.
2. Save the compiler's CR and RCR.
3. Write the KERNAL CR first (bank 0, so the wrapper stays visible), then the
KERNAL RCR. The KERNAL CR/RCR are the values BASIC had when the program
started.
4. `CLI` (the KERNAL runs in its own default mapping with IRQs enabled), then
`JSR` through an indirect jump to the routine.
5. `PHP`/`SEI`, save A and the result P.
6. Write the compiler RCR first (restoring bottom common), then the compiler
CR (which may select bank 1).
7. Return the result N/V/Z/C merged with the caller's I flag.

The ordering in steps 3 and 6 matters: whenever CR selects bank 1, bottom
common RAM must already be enabled, or the wrapper's own code disappears.

The wrapper code and its variables must be in bottom common RAM. Hardware stack
cost is the caller's return address plus one more (`kc_jump`), on top of the
routine's own usage. `kc_target` must not sit at `$xxFF` (6502 `JMP ($xxFF)`
bug); the source has a link-time `.assert`.

The probe also has `kcall_inline`, a compact call form (`JSR kcall_inline`
followed by `.word target`, 5 bytes per call site) that preserves A/X/Y. It was
needed to fit the probe inside the 8K common window.

## Compiler Mappings Tested

| State | CR | RCR (low bits) | Meaning |
|---|---|---|---|
| 0 | `$0E` | `%0110` | RAM bank 0 to `$BFFF`, system ROM high, I/O, 8K bottom common |
| 1 | `$4E` | `%0110` | Same with RAM bank 1 |

RCR bits 6–7 (VIC bank) are carried over from the entry value.

## Results

All checks pass; both per-state result bytes are `$FF`.

| Bit | Check | State 0 | State 1 |
|---|---|---|---|
| `$01` | Every CHROUT returns C=0 | pass | pass |
| `$02` | `OPEN 15,8,15` + `CHKIN 15` return C=0 | pass | pass |
| `$04` | Status text starts with two digits and ends in CR | `00, OK,00,00` | `00, OK,00,00` |
| `$08` | `LDTND` (`$98`) = 0 after CLRCHN/CLOSE on the success path | pass | pass |
| `$10` | `CHKIN` on absent device 30 returns C=1, A=5 | pass | pass |
| `$20` | `LDTND` = 0 after CLRCHN/CLOSE on the failure path | pass | pass |
| `$40` | CR/RCR/P0/P1 identical after **every** wrapper call | pass | pass |
| `$80` | `$4000` still shows this bank's marker after all calls | pass | pass |

`READST` after the failed `CHKIN` returns `$80` (device not present) from both
states, so ST also survives the round trip. Note that `OPEN` with no filename
sends nothing on the serial bus, so the device-not-present error surfaces at
`CHKIN`, not `OPEN`. Compiler disk code must check both.

The x128 screenshot (`build/kernal-probe-vicii.png`) shows one CHROUT line per
state, both drive-8 status lines, and `NOVA128 KERNAL PROBE OK`.

### Negative control

Changing the wrapper's flag merge from `AND #$FB` to `AND #$FA` (dropping the
routine's carry) makes both state results `$EF` and the verifier fails with
`CHKIN carry was clear`. The checks are therefore not vacuous.

## Open Risks

- **NMI:** RESTORE-key NMIs cannot be masked. An NMI taken in compiler state 1
has not been tested. E01-S05 should either prove the KERNAL NMI path under
bank 1 or install a common-RAM NMI handler.
- **IRQ in compiler mappings:** still masked by policy (see
`docs/mmu-probe.md`).
- **Fast serial / other drives:** only a 1541 on device 8 was tested; the
1571/1581 burst protocol is not exercised.
- VICE-only evidence; physical hardware is E08-S06.

## Reproducing

```powershell
scripts/build-kernal-probe.ps1 # PRG + fresh build/kernal-probe.d64
scripts/verify-kernal-probe.ps1 # true-drive autostart, remote checks, screenshot
```

The verifier polls `kprobe_complete` over the remote monitor because loading
through true drive emulation takes a variable number of seconds. The monitor
transcript is saved to `build/kernal-probe-remote.txt`.

+ 380
- 0
newLang/docs/language-spec.md Vedi File

@@ -0,0 +1,380 @@
---
type: language-specification
source-path: approved feature requests and expert design plan
ingested-at: 2026-11-07
version: 0.1-draft
---

# Nova128 Language Specification

Status: approved version 1 baseline. Changes to visible syntax or semantics
require a recorded decision. `Nova128` is the approved project name.

## 1. Design Goals

Nova128 is a small, predictable, statically typed object-oriented language that
can be compiled on a Commodore 128 with bounded memory.

Version 1 prioritizes:

- understandable generated 8502 code;
- deterministic memory and execution costs;
- useful encapsulation and interface polymorphism;
- source that is comfortable to enter on a Commodore keyboard;
- diagnostics that remain readable on a 40-column display;
- a compiler architecture that fits within the C128's banked memory.

Compactness is a product constraint. A feature is not part of version 1 unless
this specification defines its syntax, semantics, diagnostics, and tests.

## 2. Source Profile

### 2.1 Physical format

- Source files are PETSCII sequential files.
- Logical lines end in carriage return (`$0D`).
- The compiler accepts a missing final carriage return.
- A source file may contain both PETSCII letter ranges `$41-$5A` and
`$C1-$DA`; the lexer normalizes them for comparison.
- The version 1 editor profile permits at most 80 bytes per logical line,
18,999 source bytes, and 599 logical lines.
- A source base name should contain at most 12 characters so `.n8`, `.err`,
and `.map` names fit the Commodore DOS filename limit.
- One source file produces one program in version 1.

### 2.2 Lexical rules

- Keywords and identifiers are case-insensitive.
- Diagnostics and map files preserve the spelling of the first declaration.
- Identifiers begin with a letter and continue with letters, digits, or `_`.
- At most 16 identifier characters are significant. Longer identifiers are an
error rather than being silently truncated.
- Spaces and tabs separate tokens.
- A newline terminates a statement or declaration. Version 1 does not continue
expressions across lines and does not allow multiple statements on one line.
- `//` begins a comment that continues through the end of the logical line.
- Decimal numbers use digits. `$` introduces hexadecimal and `%` introduces
binary.
- String literals use double quotes. Version 1 escape sequences are `\"`,
`\\`, `\n`, and `\r`.
- Character literals use single quotes and contain exactly one PETSCII
character or one supported escape: `\'`, `\\`, `\n`, or `\r`.
- Reserved punctuation is `(`, `)`, `[`, `]`, `,`, `.`, `:`, `=`, `+`, `-`,
`*`, `/`, `<`, `>`, `<>`, and the single quote that delimits character
literals.
- Boolean operators are the words `and`, `or`, and `not`; `&&` and `||` are
not aliases.

## 3. Types and Values

### 3.1 Primitive types

| Type | Size | Values |
|---|---:|---|
| `boolean` | 1 byte | `false` (`0`) or `true` (`1`) |
| `byte` | 1 byte | `0` through `255` |
| `char` | 1 byte | One PETSCII byte |
| `integer` | 2 bytes | Signed two's-complement `-32768` through `32767` |
| `word` | 2 bytes | Unsigned `0` through `65535` |

String literals are length-address pairs in generated read-only data. General
mutable string variables are deferred. `print` accepts a string literal or a
supported scalar value.

### 3.2 Reference types

- Each class name defines a concrete object-reference type.
- Each interface name defines an interface-reference type.
- A concrete object reference is logically an address or `null`.
- An interface reference identifies both an object and its implementation of a
particular interface, or is `null`.
- Reference assignment copies identity; it does not copy object fields.
- Equality and inequality compare object identity.

### 3.3 Arrays

- Version 1 supports fixed-size arrays with a compile-time positive length,
written as `type[length]`.
- Arrays may be fields or local variables.
- Array indexing is zero-based.
- Bounds checking is controlled by the compiler's debug/release option.
- Arrays of interface values use the interface representation defined by the
ABI; language code does not observe its byte layout.

### 3.4 Conversions

- `byte` widens implicitly to `word` or `integer`.
- A character literal has type `char`. `char` does not silently become text or
a number in version 1.
- `boolean` never converts to or from a numeric type implicitly.
- `word` and `integer` do not convert implicitly in either direction.
- A class reference converts implicitly to an interface reference only when
the class implements that interface.
- An interface reference never converts implicitly to a concrete class.
- `null` is assignable to any class or interface reference.

## 4. Program Structure

A file contains zero or more interface declarations, zero or more class
declarations, and exactly one program declaration. Declarations may refer to
types declared later in the file.

Simplified grammar uses `{x}` for repetition and `[x]` for an optional part:

```text
file = { interface-decl | class-decl }, program-decl, EOF ;

interface-decl = "interface", identifier, EOL,
{ method-signature },
"end", "interface", EOL ;

class-decl = "class", identifier,
[ "implements", type-list ], EOL,
{ field-decl | method-decl },
"end", "class", EOL ;

program-decl = "program", identifier, EOL,
{ variable-decl | statement },
"end", "program", EOL ;

method-signature = "method", identifier, "(", [ parameter-list ], ")",
[ "as", type ], EOL ;

method-decl = [ access ], "method", identifier, "(",
[ parameter-list ], ")", [ "as", type ], EOL,
{ variable-decl }, { statement },
"end", "method", EOL ;

field-decl = [ access ], type, identifier, EOL ;
variable-decl = "var", identifier, "as", type, EOL ;
access = "public" | "private" ;
type-list = identifier, { ",", identifier } ;
```

The complete parser may use equivalent productions, but accepted and rejected
programs must follow the behavior in this specification.

## 5. Interfaces

### 5.1 Declaration

An interface contains method signatures only:

```text
interface Printable
method print()
end interface
```

Interfaces cannot declare fields, constructors, destructors, method bodies, or
access modifiers. Interface inheritance and default methods are deferred.

### 5.2 Implementation

A class implements one or more interfaces with a comma-separated list:

```text
class Counter implements Printable, Resettable
```

For every interface method, the class must provide exactly one public method
with the same case-insensitive name, parameter count, parameter types, and
return type. Version 1 has no method overloading, so a class cannot declare two
methods with the same name.

If two implemented interfaces require the same signature, one class method may
satisfy both. If they use the same name with incompatible signatures, the class
declaration is an error.

### 5.3 Interface calls

Calls made through a concrete class type are statically bound. Calls made
through an interface type are dynamically dispatched to the object's class
implementation.

```text
var output as Printable
output = new Counter(10)
output.print()
```

Calling a method on a `null` interface reference is a runtime fault in debug
mode. Release behavior must still terminate through the defined runtime-error
path rather than jumping through address zero.

## 6. Classes and Objects

### 6.1 Fields and access

- Fields default to `private`.
- Methods default to `public`.
- Private members are accessible only inside methods of their declaring class.
- Version 1 has no class inheritance, protected access, static fields, or
static methods.

### 6.2 Methods

- An instance method has an implicit `self` reference.
- Parameters are passed by value. Passing a reference copies the reference.
- Local variables must be declared before executable statements in a method.
- A non-returning method omits `as type`.
- A returning method must return a compatible value on every reachable path.
- Recursion is permitted by the language but bounded by the generated
program's hardware and software stacks.

### 6.3 Construction and destruction

- `init` is the optional constructor name. A class may declare at most one.
- `destroy` is the optional destructor name. It takes no explicit parameters
and returns no value.
- `new Class(args)` allocates zero-initialized storage and then invokes `init`.
- If allocation fails, the runtime follows the defined out-of-memory path and
does not call `init`.
- `delete expression` invokes `destroy` when present and then releases storage.
- Deleting `null` is a no-op.
- Deleting through an interface reference acts on the underlying object.
- Using a reference after deletion is erroneous; version 1 does not attempt
complete alias or lifetime proof.

## 7. Statements

Version 1 statements are:

```text
assignment target = expression
call expression.method(arguments)
if if expression
statements
[else
statements]
end if
while while expression
statements
end while
for for name = expression to expression [step expression]
statements
end for
return return [expression]
print print expression
delete delete expression
```

Conditions must have type `boolean`; numeric values are not truth values.
`and` and `or` short-circuit from left to right. A `for` loop evaluates its end
and step expressions once before the first iteration.

## 8. Expressions

From highest to lowest precedence:

1. Parentheses, member access, calls, and indexing
2. Unary `not` and unary `-`
3. `*` and `/`
4. `+` and `-`
5. `<`, `<=`, `>`, and `>=`
6. `=`, `<>`
7. `and`
8. `or`

Arithmetic is performed in the operand type after permitted widening. Version
1 arithmetic overflow always wraps in the destination's two's-complement or
unsigned representation. Debug and release builds have identical overflow
semantics. Division by zero follows the runtime-error path.

## 9. Names and Scopes

- Top-level interface and class names share one type namespace.
- Field and method names occupy separate namespaces within a class.
- Parameters and locals share a method-local namespace.
- A local or parameter may shadow a field; `self.field` selects the field.
- Duplicate declarations in the same namespace are errors.
- Forward references to top-level types and methods are permitted because the
compiler's declaration pass records signatures before bodies are compiled.

## 10. Built-in Operations

Version 1 reserves these operations:

- `print value` — output a string literal, `char`, `byte`, `word`, `integer`,
or `boolean` through the runtime's KERNAL-compatible text layer.
- `new Class(args)` — allocate and construct an object.
- `delete reference` — destroy and release an object.

Additional machine and KERNAL facilities will be introduced through explicit
runtime APIs rather than unrestricted inline assembly in version 1.

## 11. Diagnostics

Diagnostics have stable codes and source positions. The screen form must fit a
40-column display:

```text
E014 TEST.N8 23 7
BYTE EXPECTED; WORD FOUND
```

The compiler writes the complete diagnostic list to `TEST.ERR`. It stops after
20 errors by default so a malformed file cannot exhaust compiler memory.

Required diagnostic categories include:

- invalid PETSCII or malformed token;
- line, file, or identifier limit exceeded;
- duplicate or unknown name;
- syntax error with expected token;
- type mismatch or illegal conversion;
- access violation;
- incomplete or conflicting interface implementation;
- missing return;
- resource limit, disk error, or output collision.

## 12. Complete Example

```text
interface Printable
method print()
end interface

interface Resettable
method reset()
end interface

class Counter implements Printable, Resettable
private word value

public method init(start as word)
self.value = start
end method

public method increment()
self.value = self.value + 1
end method

public method print()
print self.value
end method

public method reset()
self.value = 0
end method
end class

program Main
var count as Counter
var output as Printable

count = new Counter(10)
count.increment()
output = count
output.print()
delete output
end program
```

## Open Questions

- Which additional KERNAL-backed library operations belong in the first
standard runtime after the compiler core is proven.
- Which runtime-fault messages fit after the generated-program memory map is
measured; stable fault codes and a map-resolvable address are required.


+ 87
- 0
newLang/docs/mmu-probe.md Vedi File

@@ -0,0 +1,87 @@
# C128 MMU and Common-RAM Probe

Story: E01-S02
Status: observed in VICE x128 3.10; not a final compiler memory map

## Registers Used

| Address | Name | Use in the probe |
|---|---|---|
| `$FF00` | CR | Bank/ROM/I/O configuration; visible in every mapping |
| `$D506` | RCR | Common-RAM size/placement; bits 6–7 (VIC bank) preserved |
| `$D507/$D508` | P0L/P0H | Page-zero pointer; write H first, L commits |
| `$D509/$D50A` | P1L/P1H | Page-one pointer; write H first, L commits |

Configurations exercised:

- `CR=$0E`: RAM bank 0, KERNAL ROM high, I/O at `$D000`.
- `CR=$4E`: the same with RAM bank 1 selected (bit 6).
- `RCR=$0E` (low bits): 8K common RAM at both bottom (`$0000–$1FFF`) and top
(`$E000–$FFFF`), always backed by bank 0.

The probe lives entirely in `$1C01–$1F46`, inside the bottom 8K common window,
so its code, data, zero page, and hardware stack stay visible with bank 1
selected.

## Observed Results

All checks pass (`mmu_probe_result = $FF`). The x128 screenshot shows
`NOVA128 MMU PROBE OK` followed by `READY.`.

| Check | Observation |
|---|---|
| Distinct banks | `$4000` holds `$11` in bank 0 and `$22` in bank 1 at the same time |
| Common RAM | A marker written in bank 0 is read and rewritten while bank 1 is selected, and the rewrite is visible from bank 0 |
| Bank-switched return | `JSR`/`RTS` with bank 1 selected returns with the same SP |
| P0 relocation, common off | P0 → bank 1 page `$20`: a write to `$00F0` appears at bank1:`$20F0` (`$77`) |
| P0 with common on | P0 → bank 1 page `$21`: a write to `$00F1` lands at **bank0**:`$21F1` (`$66`); logical `$00F1` and bank1:`$21F1` are unchanged |
| P1 with common on | P1 → bank 1 page `$21`: `PHA` lands at **bank0**:`$2100+SP` (`$5A`); bank0 page 1 and the bank-1 sentinel (`$33`) are unchanged; `PLA` returns the pushed value |
| Restoration | CR, RCR, P0L/H, P1L/H read back equal to their entry values before `PLP` |
| MMU write audit | 42 stores to `$FF00`/`$D500–$D50B` between entry and done, all from probe code; only `$FF00` and `$D506–$D50A` are written |

### Common-RAM priority model

The original probe assumed common RAM would *cancel* a page pointer aimed at
bank 1. That was wrong. The observed rule is:

> The page pointer's page translation always applies. When the logical page
> (0 or 1) lies inside the active bottom common window, the pointer's bank
> bit is ignored and the access goes to bank 0 at the translated page.

Consequences for the compiler:

1. With bottom common enabled, P0/P1 can relocate zero page or the stack only
within bank 0. Nova128 should keep both pointers in bank 0.
2. Relocating P0/P1 into bank 1 requires bottom common disabled, which also
removes common visibility of the code doing the switch. Avoid it.
3. A relocated page swaps with physical page 0/1 at the target address, so a
relocation target must not overlap live compiler data.

## Interrupt Policy

The probe saves P with `PHP`, executes `SEI` before the first MMU write, and
does not restore the caller's I flag (`PLP`) until every MMU register has been
restored and checked. No KERNAL call occurs while a non-default mapping is
active. Taking an IRQ with bank 1 selected was deliberately **not** tested; the
`mmu.s` wrappers must either keep IRQs masked across non-default mappings or
E01-S03/E01-S05 must prove a common-RAM IRQ path.

## Hardware Caveat

These facts are VICE x128 3.10 observations. Physical-hardware confirmation
belongs to E08-S06.

## Reproducing

```powershell
scripts/build-mmu-probe.ps1 # assemble + link, checks it fits below $2000
scripts/run-mmu-probe.ps1 # visible run, writes build/mmu-probe-vicii.png
scripts/verify-mmu-probe.ps1 # remote-monitor result block + MMU write audit
```

`verify-mmu-probe.ps1` builds `build/mmu-probe-trace.gen.txt` from
`monitor/mmu-probe-trace.txt` plus the current `mmu_probe_done` address. The
`$FF00` store trace is armed only between probe entry and done, because the
KERNAL's common-RAM fetch helper at `$02A5/$02AB` writes `$FF00` hundreds of
times during boot. The full trace is saved to `build/mmu-probe-trace-dump.txt`.
For interactive debugging, use `monitor/mmu-probe-debug.txt`.

+ 96
- 0
newLang/docs/platform-display-clock.md Vedi File

@@ -0,0 +1,96 @@
# C128 Display and Clock Probe

Story: E01-S04
Status: observed in VICE x128 3.10; not a final compiler memory map

## Primary Platform Facts

The *Commodore 128 Programmer's Reference Guide* documents:

- `$00D7` (`MODE`) bit 7: `$00` selects the active 40-column editor and `$80`
selects the active 80-column editor.
- `$FF5F` (`SWAPPER`): switches the C128 editor's 40/80-column local state.
- `$D030` bit 0: set selects 2 MHz and disables VIC video; clear selects 1 MHz
and permits VIC video.
- `$D030` bit 1 is a chip-test facility and must remain clear.
- I/O accesses in 2 MHz mode are stretched to remain synchronized with 1 MHz
I/O devices.

Sources:

- *Commodore 128 Programmer's Reference Guide*, SWAPPER, page 449.
- Same guide, 8564 VIC “2 MHz Operation” and “System Clock Control,” pages
589–590: <https://www.pagetable.com/docs/Commodore%20128%20Programmer%27s%20Reference%20Guide.pdf>
- Installed VICE 3.10 manual, C128 options `-40col` and `-80col`.

## Probe Policy

`src/platform/display-clock.s` follows the policy approved by D112:

1. Respect the editor/display already selected by the user.
2. Send all text through KERNAL `CHROUT` (`$FFD2`).
3. Force the conservative 1 MHz baseline before inspecting the display.
4. In 40-column mode, stay at 1 MHz so VIC output remains visible.
5. In 80-column mode, set `$D030` bit 0, perform KERNAL output at 2 MHz, and
then restore 1 MHz before returning to BASIC.
6. If display mode or fast-mode activation is uncertain, use 1 MHz. This is the
deterministic fallback for the compiler.

The probe does not call SWAPPER because the compiler must not override the
user's selected display. SWAPPER remains the documented mechanism if a future,
separately approved feature explicitly changes displays.

## Observed Results

The verification script boots x128 independently with `-40col` and `-80col`.

| Mode | MODE captured | Result | Fast observed | Final `$D030` bit 0 | Trace |
|---|---:|---:|---:|---:|---|
| 40-column | `$00` | `$44` | `$00` | `$00` | `$1C2E` then `$1C8C`; never `$1C49` |
| 80-column | `$80` | `$85` | `$01` | `$00` | `$1C3D`, `$1C49`, then `$1C8C` |

VICE reads `$FC` from `$D030` after fallback because unconnected upper bits
read high; the meaningful speed bit 0 and test bit 1 are both clear. The probe's
`final_clock` field stores the masked speed bit and is `$00` in both modes.

Result bit meanings are probe-local: bit 7 is the 80-column path, bit 6 is the
40-column path, bit 2 proves ordinary semantic RAM markers survived, and bit 0
proves the 2 MHz control bit read back set.

The 40-column screenshot contains:

```text
NOVA128 40 COL KERNAL OK
1MHZ FALLBACK OK
```

The 80-column screenshot contains:

```text
NOVA128 80 COL KERNAL OK
2MHZ KERNAL IO OK
RESTORED 1MHZ
```

## VDC RAM Boundary

The probe never reads or writes VDC address/data registers `$D600/$D601`.
Machine-readable state lives in normal CPU RAM at `$1D0F–$1D14`; the build
script rejects executable source references to `$D600/$D601`. In 80-column
mode, only the C128 KERNAL/editor owns display transfer to VDC-local RAM.

This establishes the architectural rule: VDC RAM is display-only and never a
home for compiler symbols, semantic tables, stacks, or intermediate data.

## Reproduction

From `newLang/`:

```powershell
./scripts/build-display-clock.ps1
./scripts/verify-display-clock.ps1
```

Generated screenshots, remote-monitor trace transcripts, PRG, map, and labels
remain under ignored `build/`.


+ 19
- 0
newLang/monitor/display-clock-trace.txt Vedi File

@@ -0,0 +1,19 @@
; E01-S04 non-stopping trace checkpoints.
; 40-column must hit $1C2E then $1C8C.
; 80-column must hit $1C3D, $1C49, then $1C8C.

radix H
device c:
sidefx off

add_label $1C0D .display_clock_entry
add_label $1C2E .mode_40_checkpoint
add_label $1C3D .mode_80_checkpoint
add_label $1C49 .fast_checkpoint
add_label $1C8C .fallback_done

trace exec .mode_40_checkpoint
trace exec .mode_80_checkpoint
trace exec .fast_checkpoint
trace exec .fallback_done


+ 9
- 0
newLang/monitor/kernal-probe-trace.txt Vedi File

@@ -0,0 +1,9 @@
; E01-S03 non-stopping monitor evidence for scripts/verify-kernal-probe.ps1.
; Records probe entry in the monitor log; results are read from the
; kprobe_* block over the remote monitor.

radix H
device c:
sidefx off

trace exec $1C0D

+ 28
- 0
newLang/monitor/minimal-debug.txt Vedi File

@@ -0,0 +1,28 @@
; E01-S01 interactive VICE monitor setup.
; Launch x128 with the built PRG and enter:
; playback "monitor/minimal-debug.txt"
;
; RUN/autostart transfers from BASIC to $1C0D. The first breakpoint proves the
; native entry path. Continue with `x`; the second breakpoint is immediately
; before RTS. At that break, `m .minimal_result .minimal_result` must show A5.

radix H
device c:
sidefx off

add_label $1C0D .minimal_entry
add_label $1C24 .minimal_done
add_label $1C3E .minimal_result

break exec .minimal_entry
break exec .minimal_done

d .minimal_entry .minimal_done
m .minimal_result .minimal_result
r

; Useful commands after the entry breakpoint:
; x
; m .minimal_result .minimal_result
; return


+ 14
- 0
newLang/monitor/minimal-trace.txt Vedi File

@@ -0,0 +1,14 @@
; Non-stopping trace setup used by scripts/verify-minimal.ps1.
; `trace` records execution in the monitor log without pausing emulation.

radix H
device c:
sidefx off

add_label $1C0D .minimal_entry
add_label $1C24 .minimal_done
add_label $1C3E .minimal_result

trace exec .minimal_entry
trace exec .minimal_done


+ 12
- 0
newLang/monitor/mmu-probe-debug.txt Vedi File

@@ -0,0 +1,12 @@
; E01-S02 interactive setup. The build label file gives the current result
; addresses. Use scripts/verify-mmu-probe.ps1 for automated assertions.

radix H
device c:
sidefx off

break exec $1C0D
watch store $FF00
watch store $D506 $D50A
r


+ 18
- 0
newLang/monitor/mmu-probe-trace.txt Vedi File

@@ -0,0 +1,18 @@
; E01-S02 non-stopping monitor evidence. Side effects are disabled before
; tracing the configuration, common-RAM, and page-pointer registers.
;
; The KERNAL's common-RAM fetch helper ($02A5/$02AB) stores to $FF00 hundreds
; of times while booting, so the $FF00 trace (#2) starts disabled and is armed
; only by the probe-entry checkpoint (#1). scripts/verify-mmu-probe.ps1 appends
; checkpoint #4 at mmu_probe_done, which disarms #2 again, to a generated copy
; under build/ because that address moves with the code.

radix H
device c:
sidefx off

trace exec $1C0D
trace store $FF00
disable 2
command 1 "enable 2"
trace store $D500 $D50B

+ 145
- 0
newLang/planning/agent-workflow.md Vedi File

@@ -0,0 +1,145 @@
---
type: agent-workflow
source-path: project collaboration requirements
ingested-at: 2026-11-07
---

# Agent Workflow

This process lets multiple agents contribute without treating chat history as
the project database.

## 1. Global Implementation Gate

The user requested approval before coding. Until the user explicitly approves
implementation, agents may improve specifications, investigate read-only facts,
or propose decisions, but must not add compiler/runtime implementation.

After approval, dependency-free stories may move from `planned` to `ready`.

## 2. Before Claiming Work

1. Read `../AGENTS.md` and the documents it routes to.
2. Run `git status --short` and preserve unrelated changes.
3. Read all files in `planning/claims/`.
4. Select one story whose dependencies are complete.
5. Confirm its expected file area does not overlap an active claim.
6. Create a claim file named exactly `<story-id>.md`.

Do not edit a shared central status table merely to claim a story. Separate
claim files reduce merge conflicts.

## 3. Claim Template

```markdown
---
story: E00-S00
owner: agent-name-or-session
status: in-progress
started: YYYY-MM-DD
depends-on: []
---

# E00-S00 Claim

## Intended change

One paragraph describing the bounded result.

## File area

- Exact files or directories expected to change

## Acceptance evidence

- [ ] Criterion copied from the backlog

## Coordination notes

- Known overlaps, messages sent, or assumptions

## Work log

- Commands and important findings

## Completion

- Tests run:
- Results:
- Files changed:
- Remaining risks:
```

Valid claim statuses are `in-progress`, `blocked`, `review`, and `done`.
Agents must not mark a claim `done` when required verification is unavailable.

## 4. Story Execution

- Work only on the claimed behavior.
- Use `apply_patch` for hand-authored file edits.
- Keep generated files under `build/`.
- Add a failing test or fixture before, or in the same change as, behavior when
practical.
- Prefer explicit error returns and bounded tables over implicit assumptions.
- Record discovered platform facts in the relevant document with evidence.
- If implementation conflicts with the spec, stop and open a decision; do not
quietly make the implementation the new specification.

## 5. Blocking and Handoff

A blocked claim must state:

- the exact failed acceptance criterion;
- evidence already collected;
- the smallest decision or external fact needed;
- safe work that remains possible without that decision.

For a handoff, leave the claim in `in-progress` or `blocked`, summarize the
current state, and list the next command or file to inspect. Do not create
ambiguous half-completed status.

## 6. Review

A reviewer should verify:

- the story did not expand scope;
- all acceptance criteria have direct evidence;
- source and generated artifacts are distinguishable;
- MMU/KERNAL behavior was tested in `x128` when relevant;
- no writable reference/editor image was modified;
- error paths and capacity boundaries were exercised;
- docs and stable diagnostic IDs remain synchronized.

When review passes, update the claim to `done` with the evidence. The backlog
itself remains a stable specification of work rather than a frequently edited
task board.

## 7. Decisions and ADR-Like Records

Small implementation choices can be recorded in a claim. A choice requires an
entry in `docs/decisions.md` when it changes:

- user-visible syntax or semantics;
- compiler/runtime ABI;
- object or interface layout;
- source, PRG, ERR, or MAP formats;
- memory ownership;
- supported hardware or editor workflow;
- version 1 scope.

Use a new `Dxxx` identifier. Mark it `Proposed` until the user approves a
visible or scope-changing decision.

## 8. Parallel Work Guidance

Safe early parallel lanes after implementation approval include:

- E01 platform probes, provided each uses separate PRG/linker targets;
- E02 diagnostic catalog and lexer fixtures;
- E06 host-side disk-copy scripts using disposable images;
- E07 test harness scaffolding that does not invent runtime addresses.

Do not parallelize two agents into the same assembly module or linker config.
The class ABI and interface ABI must be frozen before multiple backend agents
depend on them.


+ 48
- 0
newLang/planning/claims/E00-S01.md Vedi File

@@ -0,0 +1,48 @@
---
story: E00-S01
owner: root
status: done
started: 2026-11-07
depends-on: []
---

# E00-S01 Claim

## Intended change

Record the user's instruction to continue as approval of the complete Nova128
plan and its documented defaults.

## File area

- `README.md`
- `docs/decisions.md`
- `docs/language-spec.md`
- `docs/architecture.md`

## Acceptance evidence

- [x] User approval is recorded in `docs/decisions.md`.
- [x] `Nova128` and `.n8` are accepted.
- [x] C128 native mode, on-machine compilation, and C64-mode Vim integration
are accepted.
- [x] Version 1 interfaces and explicit memory management are accepted.

## Coordination notes

The user replied `continue` immediately after being asked to approve the
Nova128 implementation. No contrary changes were supplied.

## Work log

- Updated product status from planning-only to approved implementation.
- Moved D100-D110 into the accepted decision table.
- Preserved open implementation questions whose answers depend on probes.

## Completion

- Tests run: documentation consistency inspection
- Results: approval gate is open; dependency-free stories may be claimed
- Files changed: README and canonical decision/spec/architecture headers
- Remaining risks: physical-hardware release policy remains open


+ 53
- 0
newLang/planning/claims/E00-S02.md Vedi File

@@ -0,0 +1,53 @@
---
story: E00-S02
owner: e00-grammar-fixtures
status: done
started: 2026-11-07
depends-on: [E00-S01]
---

# E00-S02 Claim

## Intended change

Create accepted and rejected Nova128 grammar fixtures plus a coverage manifest
and structural validator. This story adds no parser or compiler implementation.

## File area

- `tests/grammar/`
- `planning/claims/E00-S02.md`

## Acceptance evidence

- [x] At least one accepted fixture covers every declaration and statement form.
- [x] Rejected fixtures cover missing terminators, invalid line continuation,
duplicate declarations, and malformed interface/class headers.
- [x] Fixtures remain within the editor source profile.
- [x] Expected symbolic diagnostic IDs are named.

## Coordination notes

- E01-S01 is active in another file area.
- Diagnostic IDs are symbolic placeholders for E02-S04; this story does not
assign numeric values.

## Work log

- Read canonical documents and compiler-design guidance.
- Confirmed E00-S01 is complete and no grammar claim exists.
- Added seven accepted fixtures and eleven rejected fixtures with diagnostic
sidecars.
- Added a production/statement coverage matrix and structural validator.

## Completion

- Tests run: `powershell -NoProfile -ExecutionPolicy Bypass -File
tests/grammar/validate.ps1`
- Results: `grammar fixtures ok: 7 accepted, 11 rejected`; longest source line
is 59 bytes and every fixture is below all editor-profile limits.
- Files changed: `tests/grammar/**`, `planning/claims/E00-S02.md`
- Remaining risks: numeric diagnostic assignments belong to E02-S04; these
fixtures intentionally specify symbolic IDs only. Parser execution evidence
cannot exist until the parser stories are implemented.


+ 52
- 0
newLang/planning/claims/E00-S03.md Vedi File

@@ -0,0 +1,52 @@
---
story: E00-S03
owner: root
status: done
started: 2026-11-07
depends-on: [E00-S01]
---

# E00-S03 Claim

## Intended change

Resolve the remaining language-level questions that would otherwise force the
lexer, code generator, or runtime-fault implementation to guess.

## File area

- `docs/decisions.md`
- `docs/language-spec.md`
- `docs/architecture.md`
- character-literal coverage under `tests/grammar/`

## Acceptance evidence

- [x] Cast, character-literal, overflow, and runtime-fault behavior is accepted,
deferred, or rejected.
- [x] No version 1 production relies on undefined behavior.
- [x] Decisions and fixtures agree.

## Coordination notes

E00-S02 completed before character-literal fixture changes began. No active
agent owns the grammar fixture area.

## Work log

- Recorded D111-D116 as baseline decisions following implementation approval.
- Deferred interface-to-class down-casts and debug overflow traps.
- Chose single-quoted PETSCII character literals and added accepted/rejected
grammar evidence.
- Required stable runtime fault codes plus a map-resolvable address.

## Completion

- Tests run: `powershell -NoProfile -ExecutionPolicy Bypass -File
tests/grammar/validate.ps1`
- Results: `grammar fixtures ok: 7 accepted, 12 rejected`
- Files changed: canonical decisions/spec/architecture, character-literal
fixtures, grammar coverage matrix, and this claim
- Remaining risks: exact runtime fault payload and table capacities depend on
the E01 memory-map probes


+ 64
- 0
newLang/planning/claims/E01-S01.md Vedi File

@@ -0,0 +1,64 @@
---
story: E01-S01
owner: e01_native_prg
status: done
started: 2026-11-07
depends-on: [E00-S01]
---

# E01-S01 Claim

## Intended change

Create and verify the smallest ca65/ld65 PRG that starts in C128 native mode,
prints a deterministic signature, records a machine-readable success byte, and
returns cleanly to BASIC.

## File area

- `src/platform/minimal.s`
- `cfg/minimal-c128.cfg`
- `scripts/build-minimal.ps1`
- `scripts/run-minimal.ps1`
- `monitor/minimal-debug.txt`
- generated files under `build/`

## Acceptance evidence

- [x] `ca65`/`ld65` builds from a clean workspace.
- [x] `x128` autostarts the PRG in C128 native mode.
- [x] The PRG writes an expected screen/message and machine-readable result.
- [x] A monitor script reaches the documented entry point.

## Coordination notes

This story will not establish MMU/common-RAM behavior or final compiler memory
addresses; those belong to E01-S02 and E01-S05.

## Work log

- Claim created after confirming E00-S01 is complete.
- Added a 64-byte PRG with a BASIC 7 `SYS 7181` header and documented 6502
instructions only.
- Added a bounded linker layout used only by this platform proof.
- Added clean build, screenshot run, remote-monitor verification, interactive
debug, and non-stopping trace paths.
- Observed the C128 BASIC 7 banner followed by `NOVA128 C128 NATIVE OK` and a
return to `READY.` in `build/minimal-vicii.png`.
- Verified trace hits at entry `$1C0D` and completion `$1C24`; remote monitor
read `$A5` from `minimal_result` at `$1C3E`.

## Completion

- Tests run: `scripts/build-minimal.ps1` twice; `scripts/run-minimal.ps1
-LimitCycles 30000000`; `scripts/verify-minimal.ps1`;
`tests/grammar/validate.ps1` as a coordination regression.
- Results: repeatable build; x128 native autostart and visible signature pass;
monitor trace contains `$1C0D` and `$1C24`; result byte is `$A5`; grammar
fixtures still report `7 accepted, 12 rejected`.
- Files changed: `src/platform/minimal.s`, `cfg/minimal-c128.cfg`, three
platform scripts, two monitor command files, and this claim.
- Remaining risks: the load address and result location are probe-local, not
the final compiler map; MMU/common-RAM behavior is intentionally untested and
belongs to E01-S02.


+ 80
- 0
newLang/planning/claims/E01-S02.md Vedi File

@@ -0,0 +1,80 @@
---
story: E01-S02
owner: e01_mmu_probe
status: done
started: 2026-11-07
depends-on: [E01-S01]
---

# E01-S02 Claim

## Intended change

Create an isolated C128 MMU probe that proves distinct RAM banks, verified
common memory, safe return across a bank switch, explicit IRQ handling, and
observed page-zero/page-one behavior.

## File area

- `src/platform/mmu-probe.s`
- `cfg/mmu-probe-c128.cfg`
- `scripts/build-mmu-probe.ps1`
- `scripts/run-mmu-probe.ps1`
- `scripts/verify-mmu-probe.ps1`
- `monitor/mmu-probe-*.txt`
- `docs/mmu-probe.md`
- `planning/claims/E01-S02.md`
- generated files under `build/`

## Acceptance evidence

- [x] Both 64K banks hold and return distinct test patterns (`$4000` = `$11`
in bank 0, `$22` in bank 1).
- [x] The chosen common region remains visible from both bank selections
(8K bottom+top common; marker round-trip passes).
- [x] Common code safely switches bank and returns with IRQ handling explicit
(`JSR`/`RTS` under bank 1 with SP unchanged; `PHP`/`SEI` ... `PLP`).
- [x] Page-zero/page-one relocation and common-priority behavior are recorded
in `docs/mmu-probe.md`.
- [x] VICE monitor evidence shows only intended MMU writes (42 stores in the
entry..done window, all from probe code, only `$FF00` and `$D506-$D50A`).

## Coordination notes

E01-S01 files are read-only inputs. KERNAL mapping wrappers belong to E01-S03
and are not implemented here. Added one paragraph to `docs/architecture.md`
§4.2 pointing to the evidence, as E01-S04 did for display/clock.

## Work log

- Consulted the Commodore 128 Programmer's Reference Guide MMU sections.
- First verified run: result `$DF`; only the page-zero common-priority bit
failed. The probe had assumed bottom common would cancel a P0 pointer aimed
at bank 1.
- Added observation bytes. VICE x128 3.10 shows the translation applies but
the bank bit is forced to 0: P0 write landed at bank0:`$21F1`; P1 `PHA`
landed at bank0:`$2100+SP`. Rewrote both checks to assert that model.
- Fixed a probe side effect: the P1 test left `$5A` at bank0:`$21F6` (BASIC
area). That byte is now saved and restored.
- Fixed the verifier: byte checks parse monitor dump rows instead of only
matching row-start addresses; removed an extra `r` command that stopped
`q` from exiting x128; replaced a text grep of register names with a real
store trace on `$FF00` and `$D500-$D50B`, armed only during the probe window
because the KERNAL `$02A5/$02AB` helper floods `$FF00` stores during boot.

## Completion

- Tests run: `scripts/verify-mmu-probe.ps1` (twice);
`scripts/run-mmu-probe.ps1`; `scripts/verify-minimal.ps1`;
`scripts/verify-display-clock.ps1`; `tests/grammar/validate.ps1`.
- Results: result `$FF`, completion `$A5`, banks `$11/$22`, pages `$77/$33`,
common-priority bank0 `$66/$5A`, 42 audited MMU stores; screenshot shows
`NOVA128 MMU PROBE OK` then `READY.`; minimal, display/clock, and grammar
regressions pass.
- Files changed: `src/platform/mmu-probe.s`, `scripts/verify-mmu-probe.ps1`,
`monitor/mmu-probe-trace.txt`, `docs/mmu-probe.md` (new),
`docs/architecture.md` §4.2, this claim.
- Remaining risks: VICE-only evidence (physical check is E08-S06). An IRQ
taken with bank 1 selected is untested; `mmu.s` must keep IRQs masked across
non-default mappings unless E01-S03/E01-S05 proves a common-RAM IRQ path.
The probe's addresses are not the final compiler map (E01-S05).

+ 74
- 0
newLang/planning/claims/E01-S03.md Vedi File

@@ -0,0 +1,74 @@
---
story: E01-S03
owner: e01_kernal_wrapper
status: done
started: 2026-11-07
depends-on: [E01-S02]
---

# E01-S03 Claim

## Intended change

Create an isolated C128 probe containing a reusable KERNAL call wrapper. From
two compiler mappings (bank 0 and bank 1 RAM with 8K bottom common), the
wrapper enters the KERNAL in the BASIC/KERNAL default mapping, passes A/X/Y in,
returns A/X/Y/C out, and restores the exact compiler MMU state. The probe uses
it for CHROUT, a drive-8 status read, and a device-not-present failure path.

## File area

- `src/platform/kernal-probe.s`
- `cfg/kernal-probe-c128.cfg`
- `scripts/build-kernal-probe.ps1`
- `scripts/verify-kernal-probe.ps1`
- `monitor/kernal-probe-*.txt`
- `docs/kernal-wrapper.md`
- `planning/claims/E01-S03.md`
- generated files under `build/`

## Acceptance evidence

- [x] Character output and a disk-status call work from both compiler bank
states (CHROUT lines visible; drive 8 returns `00, OK,00,00` from both).
- [x] MMU state before and after each wrapper is identical (CR/RCR/P0/P1
compared after every call; `$4000` bank marker intact).
- [x] Carry/status and error values survive the wrapper contract (CHKIN on
device 30: C=1, A=5, READST `$80` in both states; carry-drop mutation is
detected).
- [x] Open channels are closed on success and failure (`LDTND` = 0 after both
paths in both states).

## Coordination notes

Builds on E01-S02 facts in `docs/mmu-probe.md`. Does not create `mmu.s` or
the final compiler map; those belong to E01-S05 and later compiler stories.

## Work log

- First layout overflowed the 8K common window by ~15 bytes; replaced the
13-byte KCALL macro with an inline-word call form (`kcall_inline`).
- Verifier autostarts a fresh D64 through a true-emulation 1541 and polls the
completion byte, since true-drive load time varies.
- Monitor `screenshot` captured the black VDC window; switched to
`-exitscreenshotvicii`.
- Observed that `OPEN` with no filename sends nothing, so device-not-present
surfaces at `CHKIN`.
- Negative control: `AND #$FB` -> `AND #$FA` in the flag merge yields state
results `$EF` and a failing verifier; source restored afterwards.

## Completion

- Tests run: `scripts/verify-kernal-probe.ps1` (three passing runs plus one
mutation run); `scripts/verify-mmu-probe.ps1`; `tests/grammar/validate.ps1`.
- Results: result `$FF`, state results `$FF/$FF`, fail A `$05/$05`, READST
`$80/$80`, status `00, OK,00,00` from both banks; screenshot shows
`NOVA128 KERNAL PROBE OK`; regressions pass.
- Files changed: `src/platform/kernal-probe.s`, `cfg/kernal-probe-c128.cfg`,
`scripts/build-kernal-probe.ps1`, `scripts/verify-kernal-probe.ps1`,
`monitor/kernal-probe-trace.txt`, `docs/kernal-wrapper.md`,
`docs/architecture.md` section 4.2, this claim.
- Remaining risks: unmaskable RESTORE NMI under bank 1 is untested (E01-S05);
only a 1541 on device 8 tested, no burst/fast serial; the probe spans
`$1C01-$1FFA`, 1018 of the 1023 bytes below `$2000`, so the real compiler must
keep only wrappers and shared state in common RAM; VICE-only evidence.

+ 68
- 0
newLang/planning/claims/E01-S04.md Vedi File

@@ -0,0 +1,68 @@
---
story: E01-S04
owner: e01_display_clock
status: done
started: 2026-11-07
depends-on: [E01-S01]
---

# E01-S04 Claim

## Intended change

Create a separate C128 platform probe that demonstrates KERNAL-compatible text
on the 40- and 80-column displays, observes safe 1/2 MHz selection behavior,
and documents a deterministic compiler fallback without using VDC RAM for
semantic state.

## File area

- `src/platform/display-clock.s`
- `cfg/display-clock-c128.cfg`
- `scripts/build-display-clock.ps1`
- `scripts/verify-display-clock.ps1`
- `monitor/display-clock-*.txt`
- `docs/platform-display-clock.md`
- generated evidence under `build/`

## Acceptance evidence

- [x] Text is legible in tested 40- and 80-column modes.
- [x] Safe clock switching conditions are documented from observed behavior.
- [x] No semantic compiler data is placed in VDC RAM.
- [x] Fallback clock/display behavior is deterministic.

## Coordination notes

E01-S02 owns MMU/common-RAM probes in separate files. This story does not
freeze the final compiler memory map or alter E01-S01.

## Work log

- Read scoped instructions, canonical documents, and compiler/VICE skills.
- Used the primary C128 guide's `$D7`, `$D030`, and KERNAL facts to define the
probe policy.
- Added an independent PRG, linker layout, clean build, remote-monitor
verification, non-stopping trace commands, and two display captures.
- Verified the 40-column path never enters the fast checkpoint.
- Verified the 80-column path observes `$D030` bit 0 set while KERNAL text is
emitted, then clears it before return.
- Statically rejected executable `$D600/$D601` references and kept semantic
markers in ordinary CPU RAM at `$1D13/$1D14`.
- Visually inspected both generated screenshots; expected messages are legible
and both sessions return to `READY.`.

## Completion

- Tests run: `scripts/build-display-clock.ps1`;
`scripts/verify-display-clock.ps1`; `tests/grammar/validate.ps1`;
`scripts/build-minimal.ps1`
- Results: 278-byte probe builds; x128 40-column result `$44` and 80-column
result `$85`; both preserve `$5A/$C3` semantic markers and leave speed bit 0
clear; trace paths and screenshots match the documented modes; grammar and
minimal-PRG regressions pass
- Files changed: separate E01-S04 source/config/scripts/trace/documentation,
the display/clock architecture paragraph, and this claim
- Remaining risks: VICE is mandatory evidence, but physical C128 validation is
still the P2 E08-S06 story; final compiler addresses remain E01-S05 work


+ 71
- 0
newLang/planning/claims/E07-S01.md Vedi File

@@ -0,0 +1,71 @@
---
story: E07-S01
owner: e07_disk_builder
status: done
started: 2026-11-07
depends-on: [E01-S01]
---

# E07-S01 Claim

## Intended change

Create and verify a PowerShell builder that always starts from a fresh copy of
the existing Vim work D81, injects currently available Nova128 artifacts, and
never changes the source image.

## File area

- `scripts/prepare-disk.ps1`
- `tests/disk/prepare-disk.tests.ps1`
- generated files under `build/`
- this claim

## Acceptance evidence

- [x] Copies `../vim/work.d81`; never writes the original.
- [x] Injects compiler/examples deterministically with `c1541`.
- [x] Rebuilding starts from a known clean image.
- [x] Script clearly reports missing tools and source images.

## Coordination notes

- E01-S01 is complete and provides `build/minimal.prg`, the only current
Nova128 executable artifact.
- E01-S02 is active in separate platform-probe files.
- This story does not add editor/compiler launchers.

## Work log

- Confirmed source D81 SHA-256 before work:
`CE4FAE640A8558977C9C0AF2FD76769C1EF997E7445000C4C7C04638D05D6081`.
- Confirmed VICE 3.10 `c1541.exe` is installed.
- Added `prepare-disk.ps1` with validated defaults, explicit source/output
separation, fresh-copy rebuilds, optional-artifact warnings, and verified
`c1541` directory entries.
- Added deterministic ASCII-to-PETSCII SEQ conversion for future `.n8`
examples, sorted by filename.
- Added an integration test that injects a compiler-shaped PRG, the E01-S01
probe, and a generated example into a disposable image.
- Deliberately damaged the first output, rebuilt it, and obtained the same
SHA-256 from the clean source copy.
- Exercised missing source, missing tool, source/output alias rejection, and
absent optional artifact paths.

## Completion

- Tests run: `tests/disk/prepare-disk.tests.ps1`;
`tests/grammar/validate.ps1`; PowerShell parser validation for both scripts;
default `scripts/prepare-disk.ps1`; `c1541 -list` and read-back inspection
- Results: tests passed; two clean preparations produced SHA-256
`208EF297611453CE739B78AF4F7DF59455107A549C1CA15E9EF6B949D56FDB68`;
source SHA-256 remained
`CE4FAE640A8558977C9C0AF2FD76769C1EF997E7445000C4C7C04638D05D6081`;
directory entries `n128c` (PRG), `n128probe` (PRG), and `demo.n8` (SEQ)
were verified
- Files changed: `scripts/prepare-disk.ps1`,
`tests/disk/prepare-disk.tests.ps1`, and this claim
- Remaining risks: the real `N128C.PRG` and production examples do not exist
yet, so the test uses the verified E01-S01 PRG as a compiler-shaped fixture;
the default run warns and continues until those optional artifacts are built


+ 9
- 0
newLang/planning/claims/README.md Vedi File

@@ -0,0 +1,9 @@
# Story Claims

Agents create one `<story-id>.md` file here using the template in
`../agent-workflow.md`. Absence of a claim does not override story dependencies
or the user's implementation-approval gate.

Do not delete completed claims; they are the durable implementation and test
history for later agents.


+ 670
- 0
newLang/planning/epics-and-stories.md Vedi File

@@ -0,0 +1,670 @@
---
type: product-backlog
source-path: language specification and compiler architecture
ingested-at: 2026-11-07
version: 0.1-draft
---

# Nova128 Epics and Stories

All stories are `planned` until the user approves implementation. Story IDs are
stable. Splitting a story creates child IDs such as `E02-S03a`; do not renumber
existing work.

## Status and Priority

- **P0:** required to prove feasibility or unblock most other work
- **P1:** required for version 1
- **P2:** valuable release hardening

A dependency is complete only when its claim is `done` with acceptance evidence.

## Epic Summary

| Epic | Outcome | Exit gate |
|---|---|---|
| E00 | Product contracts are frozen enough to implement | User approves plan and provisional defaults |
| E01 | C128 platform mechanisms are proven | Stable compiler memory-map inputs and test mailbox |
| E02 | Source can be read, tokenized, diagnosed, and represented | Vim-created token fixture passes on x128 |
| E03 | Programs parse and type-check | Procedural semantic fixtures pass |
| E04 | Procedural programs compile and run | Control-flow and call examples pass end to end |
| E05 | Classes and deterministic objects work | Two independent objects construct, mutate, and delete |
| E06 | Interfaces and dynamic dispatch work | Multiple interface implementations dispatch correctly |
| E07 | Editor-to-compiler workflow is safe and repeatable | Vim-created source builds from a copied D81 |
| E08 | Version 1 is hardened and packaged | Regression, stress, docs, and release gates pass |

---

## E00 — Product and Contract Freeze

Purpose: turn provisional assumptions into explicit user-approved contracts.
Primary files: `docs/`, `README.md`, `AGENTS.md`.

### E00-S01 — Approve product defaults (P0)

**Depends on:** none

**Story:** As the product owner, I want the working name, source suffix, target,
editor strategy, interface scope, and ownership model confirmed so agents do
not implement mutually incompatible products.

**Acceptance criteria:**

- User approval or requested changes are recorded in `docs/decisions.md`.
- The language name and source suffix have an explicit status.
- C128 native mode, on-machine compilation, and C64-mode Vim integration have
explicit status.
- Version 1 interface and memory-management scope is explicit.

### E00-S02 — Freeze source grammar fixtures (P1)

**Depends on:** E00-S01

**Story:** As a parser implementer, I want accepted and rejected source fixtures
for every grammar production so parser behavior is testable without guessing.

**Acceptance criteria:**

- At least one accepted fixture covers every declaration and statement form.
- Rejected fixtures cover missing terminators, invalid line continuation,
duplicate declarations, and malformed interface/class headers.
- Fixtures remain within the editor source profile.
- Expected diagnostic IDs are named even if numeric assignments come later.

### E00-S03 — Resolve specification open questions (P1)

**Depends on:** E00-S01

**Story:** As a language implementer, I want cast, char-literal, overflow, and
runtime-fault decisions resolved before their code paths are written.

**Acceptance criteria:**

- Each open language-spec question is accepted, deferred, or rejected.
- No version 1 grammar production references undefined behavior.
- Changes are reflected in fixtures and decisions.

---

## E01 — C128 Platform Foundation

Purpose: prove the hardware/emulator facts on which every assembly module
depends. Primary files: `src/common/`, `src/compiler/mmu.s`, `cfg/`, `tests/platform/`,
`monitor/`, and story-specific probe targets.

### E01-S01 — Minimal native C128 PRG (P0)

**Depends on:** E00-S01

**Story:** As a compiler developer, I want a minimal ca65/ld65 C128-native PRG
that starts, writes a deterministic signature, and exits predictably.

**Acceptance criteria:**

- `ca65`/`ld65` builds from a clean workspace.
- `x128` autostarts the PRG in C128 native mode.
- The PRG writes an expected screen/message and machine-readable result.
- A monitor script reaches the documented entry point.

### E01-S02 — MMU and common-RAM probe (P0)

**Depends on:** E01-S01

**Story:** As a memory architect, I want verified bank configurations and a
common-memory trampoline so bank switching cannot be based on C64 assumptions.

**Acceptance criteria:**

- Both 64K banks are written/read with distinct patterns.
- The chosen common region remains visible across configurations.
- Code safely switches banks and returns with interrupts handled explicitly.
- Page-zero/page-one behavior is recorded.
- VICE watchpoints show only the intended MMU writes.

### E01-S03 — KERNAL mapping wrapper probe (P0)

**Depends on:** E01-S02

**Story:** As an I/O implementer, I want a wrapper that enters KERNAL routines
from compiler mappings and restores state afterward.

**Acceptance criteria:**

- Character output and a disk-status call work from both compiler bank states.
- MMU state before and after each wrapper is identical.
- Carry/status and error values survive the wrapper contract.
- Open channels are closed on success and failure.

### E01-S04 — Clock and display-mode probe (P1)

**Depends on:** E01-S01

**Story:** As a user, I want compiler output to work on either C128 display
without unsafe 2 MHz assumptions.

**Acceptance criteria:**

- Text is legible in tested 40- and 80-column modes.
- Safe clock switching conditions are documented from observed behavior.
- No semantic compiler data is placed in VDC RAM.
- The fallback clock/display behavior is deterministic.

### E01-S05 — Freeze memory-map inputs and test mailbox (P0)

**Depends on:** E01-S02, E01-S03, E01-S04

**Story:** As downstream agents, we need agreed linker regions, reserved zero
page, and a test mailbox so independent modules share one platform contract.

**Acceptance criteria:**

- `docs/architecture.md` lists verified regions and reservations.
- `cfg/compiler.cfg` can link a capacity-checking skeleton.
- `src/common/c128.inc` defines observed hardware/KERNAL constants.
- The test mailbox address and protocol are documented and exercised.

---

## E02 — Source, Lexing, Names, and Diagnostics

Purpose: consume the exact files produced by Vim and establish bounded compiler
tables. Primary files: `src/compiler/source.s`, `lexer.s`, `names.s`,
`diagnostics.s`, `src/common/tokens.inc`, `errors.inc`, `tests/lexer/`.

### E02-S01 — Buffered PETSCII source reader (P0)

**Depends on:** E01-S03, E01-S05

**Story:** As the compiler, I want a rewindable SEQ source stream with exact
line/column tracking.

**Acceptance criteria:**

- Reads Vim-created SEQ source without host conversion.
- Accepts `$0D` lines and a missing final `$0D`.
- Enforces line/file limits with stable errors.
- Rewinds identically for all compiler passes.
- Disk failure closes channels and leaves no partial output.

### E02-S02 — PETSCII lexer (P0)

**Depends on:** E02-S01, E00-S02

**Story:** As a parser, I want normalized, positioned tokens for the complete
version 1 lexical grammar.

**Acceptance criteria:**

- Both supported PETSCII letter ranges compare case-insensitively.
- Keywords, identifiers, numbers, strings, operators, comments, EOL, and EOF
match the specification.
- Invalid bytes and overlong identifiers report exact positions.
- Token dump of a Vim-created fixture matches a golden expected file.

### E02-S03 — Name interning and bounded tables (P1)

**Depends on:** E01-S05, E02-S02

**Story:** As semantic passes, I want compact interned names with deterministic
capacity errors.

**Acceptance criteria:**

- Equal normalized identifiers share an ID.
- First-declaration spelling is preserved for diagnostics/MAP output.
- Hash collisions are tested.
- Full tables report a resource error without memory corruption.

### E02-S04 — Stable diagnostic catalog and ERR writer (P1)

**Depends on:** E01-S03, E00-S02

**Story:** As a user and test author, I want stable diagnostic IDs, compact
screen messages, and complete ERR output.

**Acceptance criteria:**

- Lexical, syntax, type, access, interface, disk, and resource categories have
stable IDs.
- Screen form fits 40 columns for representative diagnostics.
- ERR output contains source, line, column, code, and message.
- Error limiting defaults to 20 and terminates cleanly.

---

## E03 — Parser and Semantic Model

Purpose: recognize complete version 1 source and reject invalid programs before
code emission. Primary files: `parser.s`, `expr.s`, `symbols.s`, `types.s`,
`classes.s`, `interfaces.s`, `tests/parser/`, `tests/semantic/`.

### E03-S01 — Top-level declaration parser (P0)

**Depends on:** E02-S02, E02-S03, E02-S04

**Acceptance criteria:**

- Parses interfaces, classes, implements lists, and exactly one program.
- Supports forward top-level type references.
- Rejects duplicate names and invalid ordering with recovery to later lines.
- Accepted/rejected fixtures from E00-S02 pass.

### E03-S02 — Method, field, and local parser (P1)

**Depends on:** E03-S01

**Acceptance criteria:**

- Parses access, signatures, parameters, fields, and leading local declarations.
- Enforces unique method names and valid `init`/`destroy` shapes.
- Assigns lexical scopes without leaking names across methods/classes.
- Reports private-member violations with source positions.

### E03-S03 — Expression and statement parser (P1)

**Depends on:** E03-S02

**Acceptance criteria:**

- Implements documented precedence and left/right associativity.
- Parses every version 1 statement form.
- Rejects multiple statements per line and cross-line expressions.
- Recovers sufficiently to report a later independent error.

### E03-S04 — Type representation and checking (P1)

**Depends on:** E03-S03

**Acceptance criteria:**

- Represents primitives, arrays, class references, interface references, and
null.
- Implements exactly the specified implicit conversions.
- Checks calls, assignments, returns, conditions, indexing, and operators.
- Reports incompatible interface assignments and missing returns.

### E03-S05 — Class layout and access model (P1)

**Depends on:** E03-S02, E03-S04

**Acceptance criteria:**

- Field offsets begin after the class-descriptor pointer and are deterministic.
- Instance size overflow/capacity is diagnosed.
- Field default privacy and method default publicity match the specification.
- MAP fixture lists sizes and offsets in declaration order.

### E03-S06 — Interface conformance model (P1)

**Depends on:** E03-S02, E03-S04

**Acceptance criteria:**

- Interface method slots follow interface source order.
- A public exact-signature method satisfies a requirement.
- One method may satisfy identical requirements from multiple interfaces.
- Same-name incompatible requirements are rejected.
- Missing, private, parameter-mismatched, and return-mismatched methods each
have fixture coverage.

---

## E04 — Procedural Code Generation

Purpose: produce runnable native code before adding heap/object complexity.
Primary files: `sizing.s`, `codegen.s`, `output.s`, `src/runtime/startup.s`,
`frames.s`, `arithmetic.s`, `print.s`, `faults.s`, `tests/codegen/`.

### E04-S01 — PRG and MAP writer (P0)

**Depends on:** E01-S05, E02-S04

**Acceptance criteria:**

- Writes a valid load-address header and deterministic sections.
- Emits a runnable startup path and clean termination.
- Refuses/handles output-name collision according to the approved policy.
- Removes or marks partial output after any failure.
- MAP records section start, size, and entry point.

### E04-S02 — Software frames and calling convention (P0)

**Depends on:** E01-S05, E03-S04

**Acceptance criteria:**

- Arguments and locals use the documented software frame.
- Nested calls preserve required registers and frame state.
- One- and two-byte returns follow the ABI.
- Stack overflow follows a stable runtime-fault path.
- ABI tests include recursion to a bounded successful depth and overflow.

### E04-S03 — Scalar expressions and assignments (P1)

**Depends on:** E04-S02

**Acceptance criteria:**

- Emits correct byte, word, integer, boolean, comparison, and assignment code.
- Short-circuit `and`/`or` avoids evaluating the unused operand.
- Division by zero faults predictably.
- Golden disassembly covers representative expression shapes.

### E04-S04 — Control flow and functions (P1)

**Depends on:** E04-S03, E03-S03

**Acceptance criteria:**

- Emits `if`, `else`, `while`, `for`, calls, and returns.
- Pass 2 sizes/addresses exactly match Pass 3 output.
- Relative branches that cannot reach use a correct long-branch expansion.
- Nested-control fixtures execute expected mailbox signatures.

### E04-S05 — Print and runtime faults (P1)

**Depends on:** E04-S03, E01-S04

**Acceptance criteria:**

- Prints literals and every supported scalar on tested displays.
- Null, bounds, divide, stack, and heap faults have stable codes.
- Runtime messages do not corrupt the program's memory mapping.
- `hello.n8` compiles and runs end to end in x128.

---

## E05 — Classes, Heap, and Concrete Methods

Purpose: implement object identity, encapsulated state, construction, and
deterministic lifetime before interface dispatch. Primary files:
`src/runtime/heap.s`, `objects.s`, compiler class/codegen modules, object tests.

### E05-S01 — Free-list heap (P0)

**Depends on:** E01-S05, E04-S05

**Acceptance criteria:**

- Allocates, zeroes, frees, splits, and coalesces blocks deterministically.
- Heap exhaustion faults without corrupting allocated blocks.
- Debug mode detects representative double-free/corrupt-header cases.
- Stress test returns the heap to its initial free capacity.

### E05-S02 — Class descriptor format (P1)

**Depends on:** E03-S05, E05-S01

**Acceptance criteria:**

- Descriptor identifies instance size and optional destructor.
- Every object begins with a valid descriptor pointer.
- Descriptor/MAP output is deterministic.
- Format reserves a documented path to interface-directory data without
introducing class inheritance.

### E05-S03 — Construction and deletion (P1)

**Depends on:** E05-S02, E04-S02

**Acceptance criteria:**

- `new` allocates, initializes the header, then calls `init` once.
- Failed allocation does not call `init`.
- `delete null` is a no-op.
- `delete` calls `destroy` once and frees the complete object.
- Constructor arguments and destructor side effects have end-to-end tests.

### E05-S04 — Fields, self, and concrete calls (P1)

**Depends on:** E05-S03, E03-S05

**Acceptance criteria:**

- `self.field` loads/stores correct offsets.
- Concrete calls use direct `JSR` and preserve the caller ABI.
- Private access is rejected at compile time.
- Two objects of one class retain independent state.
- `counter.n8` compiles and runs with the expected result.

### E05-S05 — Fixed arrays and debug bounds (P2)

**Depends on:** E04-S03, E05-S04

**Acceptance criteria:**

- Local and field arrays use specified element sizes.
- Constant and computed indices address correct elements.
- Debug bounds checks fault on low/high invalid indices.
- Release behavior matches the approved bounds-check policy.

---

## E06 — Interfaces and Dynamic Dispatch

Purpose: deliver the user-requested interface polymorphism. Primary files:
compiler/runtime `interfaces.s`, class descriptors, code generation, ABI tests.

### E06-S01 — Freeze interface ABI (P0)

**Depends on:** E01-S05, E03-S06, E05-S02

**Acceptance criteria:**

- Four-byte interface value layout is documented and tested little-endian.
- Interface slot and per-class table layouts are frozen.
- Conversion, parameter, return, local, field, and array storage rules are
specified.
- Register clobbers and the four-byte return convention are finalized.

### E06-S02 — Indirect-call trampoline (P0)

**Depends on:** E06-S01, E04-S02

**Acceptance criteria:**

- Calls at least three target methods selected at runtime.
- Returns correctly through nested and recursive calls.
- Preserves documented registers, software frame, and interrupt state.
- Null/invalid targets use a fault path rather than executing address zero.
- Hardware-stack boundary tests pass.

### E06-S03 — Emit interface implementation tables (P1)

**Depends on:** E06-S01, E05-S04

**Acceptance criteria:**

- Each class emits one method table per implemented interface.
- Table addresses and slots appear in MAP output.
- One method can populate slots for multiple compatible interfaces.
- Different classes implementing one interface emit distinct correct tables.

### E06-S04 — Concrete-to-interface conversion (P1)

**Depends on:** E06-S03

**Acceptance criteria:**

- Assignment creates the correct object/table pair.
- `null` conversion produces a canonical null interface value.
- Passing, returning, storing, and array-copying interface values preserve all
four bytes.
- Invalid class-to-interface conversion is a compile-time error.

### E06-S05 — Interface method calls (P1)

**Depends on:** E06-S02, E06-S04

**Acceptance criteria:**

- Call uses the compile-time interface slot and runtime table pointer.
- `self` is the underlying object, not the fat-reference address.
- Two classes dispatch the same interface call to different methods.
- One class implements and dispatches at least two interfaces.
- Arguments and one-/two-byte returns work through interface calls.

### E06-S06 — Interface lifetime and failure paths (P1)

**Depends on:** E06-S05, E05-S03

**Acceptance criteria:**

- `delete` through an interface invokes the class destructor and frees once.
- `delete null-interface` is a no-op.
- Null calls fault predictably.
- A debug use-after-free scenario produces a detectable failure where the
approved debug policy promises detection.
- `objects.n8` covers multiple interfaces end to end.

---

## E07 — Vim and Disk Workflow

Purpose: make source creation and compilation repeatable without modifying the
original editor. Primary files: `scripts/`, `docs/editor-workflow.md`,
integration fixtures, generated `build/` artifacts.

### E07-S01 — Disposable development image builder (P0)

**Depends on:** E01-S01

**Acceptance criteria:**

- Copies `../vim/work.d81`; never writes the original.
- Injects compiler/examples deterministically with `c1541`.
- Rebuilding starts from a known clean image.
- Script clearly reports missing tools and source images.

### E07-S02 — Sequential editor/compiler launchers (P1)

**Depends on:** E07-S01, E04-S01

**Acceptance criteria:**

- Editor launcher uses `x64sc` and autostarts Vim.
- Compiler launcher uses `x128` and autostarts the compiler.
- Launchers wait for process/image flush before returning.
- Documentation and warnings prohibit simultaneous writable attachment.

### E07-S03 — Vim source round-trip (P0)

**Depends on:** E07-S02, E02-S02

**Acceptance criteria:**

- A source file created entirely in Vim tokenizes on C128 without host
conversion.
- Reopening it in Vim preserves content.
- Extracted bytes match the expected PETSCII/CR fixture.
- Both supported PETSCII letter ranges are exercised.

### E07-S04 — Edit/compile/run walkthrough (P1)

**Depends on:** E07-S03, E06-S06

**Acceptance criteria:**

- A new user can edit, compile, inspect diagnostics, and run an interface-based
sample from documented commands.
- Failure recovery for syntax errors and output collisions is documented.
- No step requires modifying `vim/` or a reference disk.

---

## E08 — Hardening and Release

Purpose: prove version 1 against limits and package a reproducible release.
Primary files: `tests/`, `monitor/`, `release/`, and final documentation.

### E08-S01 — Automated x128 regression harness (P0)

**Depends on:** E01-S05, E02-S04

**Acceptance criteria:**

- Creates a fresh D81 per test run.
- Enforces time/cycle limits and captures machine-readable results.
- Extracts PRG/ERR/MAP outputs and compares expected artifacts.
- A failed test identifies the fixture and observed signature.

### E08-S02 — Golden code and disassembly suite (P1)

**Depends on:** E04-S04, E05-S04, E06-S05

**Acceptance criteria:**

- Covers scalar operations, branches, frames, object fields, construction,
deletion, conversions, and interface dispatch.
- Expected bytes/disassembly are reviewed rather than blindly regenerated.
- All emitted opcodes are documented 8502/6502 instructions.

### E08-S03 — Capacity and failure stress suite (P1)

**Depends on:** E05-S05, E06-S06, E08-S01

**Acceptance criteria:**

- Exercises maximum line/file/identifier limits and every compiler table.
- Exercises disk errors, heap exhaustion, stack exhaustion, and error limit.
- Exercises many classes, interfaces, slots, fields, locals, and nested calls.
- Every limit fails cleanly with its stable code.

### E08-S04 — Example and conformance suite (P1)

**Depends on:** E06-S06, E08-S01

**Acceptance criteria:**

- Includes Hello World, Counter, two class instances, two implementations of
one interface, one class with two interfaces, constructor/destructor, arrays,
and deliberate compile/runtime failures.
- Each example states expected output.
- Every normative language-spec section maps to at least one test or an
explicit non-executable review check.

### E08-S05 — Release image and documentation (P1)

**Depends on:** E07-S04, E08-S02, E08-S03, E08-S04

**Acceptance criteria:**

- Clean build produces compiler PRG, copied development D81, checksums, and
release notes.
- User guide covers editor workflow, language basics, interfaces, diagnostics,
limits, and recovery.
- Developer guide covers build, memory map, ABI, tests, and adding diagnostics.
- Release artifacts are regenerated from source without manual image edits.

### E08-S06 — Physical C128 smoke test (P2)

**Depends on:** E08-S05; physical hardware availability

**Acceptance criteria:**

- Compiler starts in native mode from the release media.
- Vim-created source compiles and the generated interface example runs.
- 40/80-column and disk behavior are recorded.
- Any emulator/hardware difference becomes a tracked story, not an undocumented
workaround.

---

## Suggested First Parallel Wave After Approval

- Agent A: E01-S01 minimal native PRG
- Agent B: E00-S02 grammar fixtures
- Agent C: E02-S04 diagnostic catalog design (documentation/fixtures only until
E01 wrappers exist)
- Agent D: E07-S01 disposable image builder

The MMU probe, ABI modules, and linker configurations require deliberate
coordination; they should not be edited concurrently by unrelated stories.

## Open Questions

- Which provisional decisions does the user want changed before E00-S01 closes?
- Is physical-hardware testing a release gate or a post-release validation?
- How many compiler-table records fit after E01 freezes the real memory map?
- Should separate compilation become the first post-version-1 epic?


+ 74
- 0
newLang/scripts/build-display-clock.ps1 Vedi File

@@ -0,0 +1,74 @@
# E01-S04: build the independent C128 display/clock probe.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$source = Join-Path $project "src\platform\display-clock.s"
$config = Join-Path $project "cfg\display-clock-c128.cfg"
$object = Join-Path $build "display-clock.o"
$program = Join-Path $build "display-clock.prg"
$map = Join-Path $build "display-clock.map"
$labels = Join-Path $build "display-clock.lbl"
$debug = Join-Path $build "display-clock.dbg"

$ca65 = (Get-Command ca65 -ErrorAction Stop).Source
$ld65 = (Get-Command ld65 -ErrorAction Stop).Source

New-Item -ItemType Directory -Force -Path $build | Out-Null
foreach ($path in @($object, $program, $map, $labels, $debug)) {
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Force
}
}

# VDC register access would make semantic storage/display behavior ambiguous.
$sourceText = Get-Content -Raw -LiteralPath $source
$codeOnly = ($sourceText -split "`n" | ForEach-Object {
($_ -split ';', 2)[0]
}) -join "`n"
if ($codeOnly -match '(?i)\$D60[01]') {
throw "display/clock probe must not access VDC address/data registers"
}

& $ca65 --cpu 6502 --debug-info -o $object $source
if ($LASTEXITCODE -ne 0) { throw "ca65 failed with exit code $LASTEXITCODE" }

& $ld65 -C $config -o $program -m $map -Ln $labels --dbgfile $debug $object
if ($LASTEXITCODE -ne 0) { throw "ld65 failed with exit code $LASTEXITCODE" }

$bytes = [IO.File]::ReadAllBytes($program)
if ($bytes.Length -lt 16) { throw "display-clock.prg is unexpectedly short" }
if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) {
throw "display-clock.prg load address is not `$1C01"
}

$labelText = Get-Content -Raw -LiteralPath $labels
$expectedLabels = @{
display_clock_entry = '001C0D'
mode_40_checkpoint = '001C2E'
mode_80_checkpoint = '001C3D'
fast_checkpoint = '001C49'
fallback_done = '001C8C'
display_result = '001D0F'
mode_observed = '001D10'
fast_observed = '001D11'
final_clock = '001D12'
semantic_a = '001D13'
semantic_b = '001D14'
}
foreach ($item in $expectedLabels.GetEnumerator()) {
$pattern = "(?im)^al\s+" + $item.Value + "\s+\." +
[regex]::Escape($item.Key) + "\s*$"
if ($labelText -notmatch $pattern) {
throw ("linker label {0} did not resolve to `${1}" -f
$item.Key, $item.Value.Substring(2))
}
}

Write-Host ("built {0} ({1} bytes)" -f $program, $bytes.Length)
Write-Host ("map {0}" -f $map)
Write-Host ("labels {0}" -f $labels)



+ 74
- 0
newLang/scripts/build-kernal-probe.ps1 Vedi File

@@ -0,0 +1,74 @@
# E01-S03: build the C128 KERNAL wrapper probe and its test disk.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$source = Join-Path $project "src\platform\kernal-probe.s"
$config = Join-Path $project "cfg\kernal-probe-c128.cfg"
$object = Join-Path $build "kernal-probe.o"
$program = Join-Path $build "kernal-probe.prg"
$map = Join-Path $build "kernal-probe.map"
$labels = Join-Path $build "kernal-probe.lbl"
$debug = Join-Path $build "kernal-probe.dbg"
$listing = Join-Path $build "kernal-probe.lst"

$ca65 = (Get-Command ca65 -ErrorAction Stop).Source
$ld65 = (Get-Command ld65 -ErrorAction Stop).Source
New-Item -ItemType Directory -Force -Path $build | Out-Null

foreach ($path in @($object, $program, $map, $labels, $debug, $listing)) {
if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
}

& $ca65 --cpu 6502 --debug-info -l $listing -o $object $source
if ($LASTEXITCODE -ne 0) { throw "ca65 failed with exit code $LASTEXITCODE" }
& $ld65 -C $config -o $program -m $map -Ln $labels --dbgfile $debug $object
if ($LASTEXITCODE -ne 0) { throw "ld65 failed with exit code $LASTEXITCODE" }

$bytes = [IO.File]::ReadAllBytes($program)
if ($bytes.Length -lt 16) { throw "kernal-probe.prg is unexpectedly short" }
if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) {
throw "kernal-probe.prg load address is not `$1C01"
}
$expectedHeader = [byte[]](0x0B,0x1C,0x0A,0x00,0x9E,0x37,0x31,0x38,0x31,0x00,0x00,0x00)
for ($i = 0; $i -lt $expectedHeader.Length; $i++) {
if ($bytes[$i + 2] -ne $expectedHeader[$i]) {
throw ("BASIC header differs at file offset {0}" -f ($i + 2))
}
}

# The last loaded byte must remain below $2000, the end of the selected 8K
# bottom-common window. File bytes after the two-byte load address begin $1C01.
$lastAddress = 0x1C01 + ($bytes.Length - 2) - 1
if ($lastAddress -ge 0x2000) {
throw ("probe ends at `${0:X4}, outside bottom common RAM" -f $lastAddress)
}

$labelText = Get-Content -Raw -LiteralPath $labels
foreach ($name in @(
'kprobe_entry','kprobe_done','kprobe_result','kprobe_complete',
'kprobe_state_result','kprobe_fail_a','kprobe_fail_p','kprobe_fail_st',
'kprobe_status_buf','kprobe_code_end','kcall')) {
if ($labelText -notmatch ("(?im)^al\s+[0-9a-f]+\s+\." + [regex]::Escape($name) + "\s*$")) {
throw "missing exported linker label $name"
}
}

# The drive-8 success path needs a real disk: a fresh D64 holding only the
# probe, so autostart loads it through true drive emulation.
$c1541 = "C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe"
if (-not (Test-Path -LiteralPath $c1541)) { throw "c1541.exe not found at $c1541" }
$disk = Join-Path $build "kernal-probe.d64"
if (Test-Path -LiteralPath $disk) { Remove-Item -LiteralPath $disk -Force }
& $c1541 -format "kprobe,kp" d64 $disk -write $program "kernal-probe" | Out-Null
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $disk)) {
throw "c1541 could not create $disk"
}

Write-Host ("built {0} ({1} bytes, ends `${2:X4})" -f $program, $bytes.Length, $lastAddress)
Write-Host ("disk {0}" -f $disk)
Write-Host ("map {0}" -f $map)
Write-Host ("labels {0}" -f $labels)


+ 64
- 0
newLang/scripts/build-minimal.ps1 Vedi File

@@ -0,0 +1,64 @@
# E01-S01: build the minimal native-mode C128 platform proof.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$source = Join-Path $project "src\platform\minimal.s"
$config = Join-Path $project "cfg\minimal-c128.cfg"
$object = Join-Path $build "minimal.o"
$program = Join-Path $build "minimal.prg"
$map = Join-Path $build "minimal.map"
$labels = Join-Path $build "minimal.lbl"
$debug = Join-Path $build "minimal.dbg"

$ca65 = (Get-Command ca65 -ErrorAction Stop).Source
$ld65 = (Get-Command ld65 -ErrorAction Stop).Source

New-Item -ItemType Directory -Force -Path $build | Out-Null

foreach ($path in @($object, $program, $map, $labels, $debug)) {
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Force
}
}

& $ca65 --cpu 6502 --debug-info -o $object $source
if ($LASTEXITCODE -ne 0) { throw "ca65 failed with exit code $LASTEXITCODE" }

& $ld65 -C $config -o $program -m $map -Ln $labels --dbgfile $debug $object
if ($LASTEXITCODE -ne 0) { throw "ld65 failed with exit code $LASTEXITCODE" }

$bytes = [IO.File]::ReadAllBytes($program)
if ($bytes.Length -lt 16) { throw "minimal.prg is unexpectedly short" }
if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) {
throw "minimal.prg load address is not `$1C01"
}

# The first BASIC line must be SYS 7181, whose target is $1C0D.
$expectedHeader = [byte[]](0x0B,0x1C,0x0A,0x00,0x9E,0x37,0x31,0x38,0x31,0x00,0x00,0x00)
for ($i = 0; $i -lt $expectedHeader.Length; $i++) {
if ($bytes[$i + 2] -ne $expectedHeader[$i]) {
throw ("BASIC header differs at file offset {0}: expected {1:X2}, got {2:X2}" -f
($i + 2), $expectedHeader[$i], $bytes[$i + 2])
}
}

$labelText = Get-Content -Raw -LiteralPath $labels
$expectedLabels = @{
minimal_entry = "001C0D"
minimal_done = "001C24"
minimal_result = "001C3E"
}
foreach ($item in $expectedLabels.GetEnumerator()) {
$pattern = "(?im)^al\s+" + $item.Value + "\s+\." + [regex]::Escape($item.Key) + "\s*$"
if ($labelText -notmatch $pattern) {
throw ("linker label {0} did not resolve to `${1}" -f $item.Key, $item.Value.Substring(2))
}
}

Write-Host ("built {0} ({1} bytes)" -f $program, $bytes.Length)
Write-Host ("map {0}" -f $map)
Write-Host ("labels {0}" -f $labels)


+ 62
- 0
newLang/scripts/build-mmu-probe.ps1 Vedi File

@@ -0,0 +1,62 @@
# E01-S02: build the isolated C128 MMU/common-RAM proof.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$source = Join-Path $project "src\platform\mmu-probe.s"
$config = Join-Path $project "cfg\mmu-probe-c128.cfg"
$object = Join-Path $build "mmu-probe.o"
$program = Join-Path $build "mmu-probe.prg"
$map = Join-Path $build "mmu-probe.map"
$labels = Join-Path $build "mmu-probe.lbl"
$debug = Join-Path $build "mmu-probe.dbg"
$listing = Join-Path $build "mmu-probe.lst"

$ca65 = (Get-Command ca65 -ErrorAction Stop).Source
$ld65 = (Get-Command ld65 -ErrorAction Stop).Source
New-Item -ItemType Directory -Force -Path $build | Out-Null

foreach ($path in @($object, $program, $map, $labels, $debug, $listing)) {
if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
}

& $ca65 --cpu 6502 --debug-info -l $listing -o $object $source
if ($LASTEXITCODE -ne 0) { throw "ca65 failed with exit code $LASTEXITCODE" }
& $ld65 -C $config -o $program -m $map -Ln $labels --dbgfile $debug $object
if ($LASTEXITCODE -ne 0) { throw "ld65 failed with exit code $LASTEXITCODE" }

$bytes = [IO.File]::ReadAllBytes($program)
if ($bytes.Length -lt 16) { throw "mmu-probe.prg is unexpectedly short" }
if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) {
throw "mmu-probe.prg load address is not `$1C01"
}
$expectedHeader = [byte[]](0x0B,0x1C,0x0A,0x00,0x9E,0x37,0x31,0x38,0x31,0x00,0x00,0x00)
for ($i = 0; $i -lt $expectedHeader.Length; $i++) {
if ($bytes[$i + 2] -ne $expectedHeader[$i]) {
throw ("BASIC header differs at file offset {0}" -f ($i + 2))
}
}

# The last loaded byte must remain below $2000, the end of the selected 8K
# bottom-common window. File bytes after the two-byte load address begin $1C01.
$lastAddress = 0x1C01 + ($bytes.Length - 2) - 1
if ($lastAddress -ge 0x2000) {
throw ("probe ends at `${0:X4}, outside bottom common RAM" -f $lastAddress)
}

$labelText = Get-Content -Raw -LiteralPath $labels
foreach ($name in @(
'mmu_probe_entry','mmu_probe_done','mmu_probe_result','mmu_probe_complete',
'mmu_probe_observed_bank0','mmu_probe_observed_bank1',
'mmu_probe_observed_p0','mmu_probe_observed_p1')) {
if ($labelText -notmatch ("(?im)^al\s+[0-9a-f]+\s+\." + [regex]::Escape($name) + "\s*$")) {
throw "missing exported linker label $name"
}
}

Write-Host ("built {0} ({1} bytes, ends `${2:X4})" -f $program, $bytes.Length, $lastAddress)
Write-Host ("map {0}" -f $map)
Write-Host ("labels {0}" -f $labels)


+ 206
- 0
newLang/scripts/prepare-disk.ps1 Vedi File

@@ -0,0 +1,206 @@
# E07-S01: create a disposable Nova128 development D81 from the Vim work disk.
[CmdletBinding()]
param(
[string]$SourceImage = "",
[string]$OutputImage = "",
[string]$C1541Path = "",
[string]$CompilerPrg = "",
[string]$ProbePrg = "",
[string]$ExamplesDirectory = "",
[switch]$RequireCompiler
)

$ErrorActionPreference = "Stop"

$project = Split-Path $PSScriptRoot -Parent
$repository = Split-Path $project -Parent
$build = Join-Path $project "build"

if ($SourceImage -eq "") {
$SourceImage = Join-Path $repository "vim\work.d81"
}
if ($OutputImage -eq "") {
$OutputImage = Join-Path $build "newlang-dev.d81"
}
if ($C1541Path -eq "") {
$C1541Path = "C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe"
}
if ($CompilerPrg -eq "") {
$CompilerPrg = Join-Path $build "n128c.prg"
}
if ($ProbePrg -eq "") {
$ProbePrg = Join-Path $build "minimal.prg"
}
if ($ExamplesDirectory -eq "") {
$ExamplesDirectory = Join-Path $project "examples"
}

$SourceImage = [IO.Path]::GetFullPath($SourceImage)
$OutputImage = [IO.Path]::GetFullPath($OutputImage)
$C1541Path = [IO.Path]::GetFullPath($C1541Path)
$CompilerPrg = [IO.Path]::GetFullPath($CompilerPrg)
$ProbePrg = [IO.Path]::GetFullPath($ProbePrg)
$ExamplesDirectory = [IO.Path]::GetFullPath($ExamplesDirectory)

if (-not (Test-Path -LiteralPath $SourceImage -PathType Leaf)) {
throw "source D81 not found: $SourceImage"
}
if (-not (Test-Path -LiteralPath $C1541Path -PathType Leaf)) {
throw "c1541 tool not found: $C1541Path"
}
if ([IO.Path]::GetExtension($OutputImage) -ine ".d81") {
throw "output image must use the .d81 extension: $OutputImage"
}
if ([string]::Equals($SourceImage, $OutputImage,
[StringComparison]::OrdinalIgnoreCase)) {
throw "output image must not be the source D81"
}

$sourceHashBefore = (Get-FileHash -LiteralPath $SourceImage -Algorithm SHA256).Hash
$outputDirectory = Split-Path $OutputImage -Parent
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null

# Only the exact generated output is removed. The source image and its folder
# are never used as deletion targets.
if (Test-Path -LiteralPath $OutputImage) {
Remove-Item -LiteralPath $OutputImage -Force
}
Copy-Item -LiteralPath $SourceImage -Destination $OutputImage

$staging = Join-Path $build "disk-staging"
New-Item -ItemType Directory -Force -Path $staging | Out-Null
$injected = New-Object System.Collections.Generic.List[string]

function Get-DiskDirectory {
$listing = (& $C1541Path $OutputImage -list 2>&1) -join "`n"
if ($LASTEXITCODE -ne 0) {
throw "c1541 could not list the copied D81:`n$listing"
}
return $listing
}

function Add-DiskFile {
param(
[Parameter(Mandatory = $true)][string]$HostPath,
[Parameter(Mandatory = $true)][string]$DiskName,
[Parameter(Mandatory = $true)][ValidateSet("prg", "seq")][string]$Type
)

if (-not (Test-Path -LiteralPath $HostPath -PathType Leaf)) {
throw "artifact disappeared before injection: $HostPath"
}
$destination = if ($Type -eq "seq") { "$DiskName,s" } else { $DiskName }
$writeOutput = (& $C1541Path $OutputImage -write $HostPath $destination 2>&1) -join "`n"
if ($LASTEXITCODE -ne 0) {
throw "c1541 failed to inject '$DiskName':`n$writeOutput"
}

$listing = Get-DiskDirectory
$pattern = '(?im)"' + [regex]::Escape($DiskName) + '"\s+' + $Type + '\s*$'
if ($listing -notmatch $pattern) {
throw "c1541 reported success but '$DiskName' ($Type) is absent from the D81"
}
$injected.Add("$DiskName ($Type)")
}

function ConvertTo-PetsciiSeq {
param(
[Parameter(Mandatory = $true)][string]$InputPath,
[Parameter(Mandatory = $true)][string]$OutputPath
)

$bytes = [IO.File]::ReadAllBytes($InputPath)
$start = 0
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and
$bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
$start = 3
}

$converted = New-Object System.Collections.Generic.List[byte]
for ($i = $start; $i -lt $bytes.Length; $i++) {
$value = $bytes[$i]
if ($value -eq 13) {
if ($i + 1 -lt $bytes.Length -and $bytes[$i + 1] -eq 10) {
continue
}
$converted.Add([byte]13)
continue
}
if ($value -eq 10) {
$converted.Add([byte]13)
continue
}
if ($value -gt 0x7F) {
throw "example source is not ASCII/UTF-8 text: $InputPath at byte $i"
}
if ($value -ge 0x61 -and $value -le 0x7A) {
$converted.Add([byte]($value - 0x20))
continue
}
if ($value -ge 0x41 -and $value -le 0x5A) {
$converted.Add([byte]($value + 0x80))
continue
}
$converted.Add([byte]$value)
}
[IO.File]::WriteAllBytes($OutputPath, $converted.ToArray())
}

if (Test-Path -LiteralPath $CompilerPrg -PathType Leaf) {
Add-DiskFile -HostPath $CompilerPrg -DiskName "n128c" -Type prg
} elseif ($RequireCompiler) {
throw "required Nova128 compiler PRG not found: $CompilerPrg"
} else {
Write-Warning "Nova128 compiler is not built yet; skipped $CompilerPrg"
}

if (Test-Path -LiteralPath $ProbePrg -PathType Leaf) {
Add-DiskFile -HostPath $ProbePrg -DiskName "n128probe" -Type prg
} else {
Write-Warning "optional native C128 probe is absent; skipped $ProbePrg"
}

if (Test-Path -LiteralPath $ExamplesDirectory -PathType Container) {
$examples = @(Get-ChildItem -LiteralPath $ExamplesDirectory -Filter "*.n8" -File |
Sort-Object Name)
foreach ($example in $examples) {
if ($example.BaseName.Length -gt 12) {
throw "example base name exceeds the 12-character source profile: $($example.Name)"
}
$diskName = $example.BaseName.ToLowerInvariant() + ".n8"
$petscii = Join-Path $staging ($example.BaseName + ".pet")
ConvertTo-PetsciiSeq -InputPath $example.FullName -OutputPath $petscii
Add-DiskFile -HostPath $petscii -DiskName $diskName -Type seq
}
if ($examples.Count -eq 0) {
Write-Warning "examples directory contains no .n8 files: $ExamplesDirectory"
}
} else {
Write-Warning "examples directory does not exist yet; skipped $ExamplesDirectory"
}

$sourceHashAfter = (Get-FileHash -LiteralPath $SourceImage -Algorithm SHA256).Hash
if ($sourceHashAfter -ne $sourceHashBefore) {
throw "source D81 changed while preparing the disposable image"
}

# A final listing proves the resulting image is readable after all writes.
$null = Get-DiskDirectory
$outputHash = (Get-FileHash -LiteralPath $OutputImage -Algorithm SHA256).Hash

Write-Host "prepared disposable D81: $OutputImage"
Write-Host "source SHA-256 unchanged: $sourceHashAfter"
if ($injected.Count -eq 0) {
Write-Host "injected: no optional Nova128 artifacts are built yet"
} else {
Write-Host ("injected: " + ($injected -join ", "))
}

[pscustomobject]@{
SourceImage = $SourceImage
OutputImage = $OutputImage
SourceHash = $sourceHashAfter
OutputHash = $outputHash
Injected = @($injected)
}


+ 40
- 0
newLang/scripts/run-minimal.ps1 Vedi File

@@ -0,0 +1,40 @@
# E01-S01: build and launch the platform proof in VICE x128.
[CmdletBinding()]
param(
[int]$LimitCycles = 30000000
)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$program = Join-Path $build "minimal.prg"
$screenshot = Join-Path $build "minimal-vicii.png"
$x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe"

& (Join-Path $PSScriptRoot "build-minimal.ps1")
if ($LASTEXITCODE -ne 0) { throw "minimal build failed" }
if (-not (Test-Path -LiteralPath $x128)) { throw "x128.exe not found at $x128" }

if (Test-Path -LiteralPath $screenshot) {
Remove-Item -LiteralPath $screenshot -Force
}

$arguments = @(
"-autostart", $program,
"-autostart-warp", "-warp",
"-sounddev", "dummy",
"-limitcycles", "$LimitCycles",
"-exitscreenshotvicii", $screenshot
)

$process = Start-Process -FilePath $x128 -ArgumentList $arguments -PassThru -Wait -WindowStyle Hidden

# VICE uses a non-zero exit status when -limitcycles ends the session. The
# screenshot and deterministic program state are the evidence, not that status.
if (-not (Test-Path -LiteralPath $screenshot)) {
throw "x128 did not create $screenshot"
}

Write-Host ("x128 exit code: {0} (cycle-limit exit may be non-zero)" -f $process.ExitCode)
Write-Host ("screenshot: {0}" -f $screenshot)


+ 27
- 0
newLang/scripts/run-mmu-probe.ps1 Vedi File

@@ -0,0 +1,27 @@
# E01-S02: build and visibly run the MMU probe in VICE x128.
[CmdletBinding()]
param([int]$LimitCycles = 30000000)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$program = Join-Path $build "mmu-probe.prg"
$screenshot = Join-Path $build "mmu-probe-vicii.png"
$x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe"

& (Join-Path $PSScriptRoot "build-mmu-probe.ps1")
if ($LASTEXITCODE -ne 0) { throw "MMU probe build failed" }
if (-not (Test-Path -LiteralPath $x128)) { throw "x128.exe not found at $x128" }
if (Test-Path -LiteralPath $screenshot) { Remove-Item -LiteralPath $screenshot -Force }

$arguments = @(
"-autostart", $program,
"-autostart-warp", "-warp", "-sounddev", "dummy",
"-limitcycles", "$LimitCycles", "-exitscreenshotvicii", $screenshot
)
$process = Start-Process -FilePath $x128 -ArgumentList $arguments -PassThru -Wait -WindowStyle Hidden
if (-not (Test-Path -LiteralPath $screenshot)) { throw "x128 did not create $screenshot" }

Write-Host ("x128 exit code: {0} (cycle-limit exit may be non-zero)" -f $process.ExitCode)
Write-Host ("screenshot: {0}" -f $screenshot)


+ 174
- 0
newLang/scripts/verify-display-clock.ps1 Vedi File

@@ -0,0 +1,174 @@
# E01-S04: build and verify display/clock behavior in both x128 modes.
[CmdletBinding()]
param(
[int]$RemotePort = 6510,
[int]$LimitCycles = 30000000
)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$program = Join-Path $build "display-clock.prg"
$traceCommands = Join-Path $project "monitor\display-clock-trace.txt"
$x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe"

& (Join-Path $PSScriptRoot "build-display-clock.ps1")
if ($LASTEXITCODE -ne 0) { throw "display/clock build failed" }
if (-not (Test-Path -LiteralPath $x128)) { throw "x128.exe not found at $x128" }

function Quote-Arg([string]$value) {
if ($value -ne "" -and $value -notmatch '[\s"]') { return $value }
$escaped = $value -replace '(\\*)"', '$1$1\"'
$escaped = $escaped -replace '(\\+)$', '$1$1'
return '"' + $escaped + '"'
}

function Start-X128([string[]]$arguments) {
$psi = [Diagnostics.ProcessStartInfo]::new()
$psi.FileName = $x128
$psi.Arguments = ($arguments | ForEach-Object { Quote-Arg $_ }) -join ' '
$psi.UseShellExecute = $true
$psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
return [Diagnostics.Process]::Start($psi)
}

function Invoke-Screenshot([string]$mode) {
$screenshot = Join-Path $build ("display-clock-{0}.png" -f $mode)
if (Test-Path -LiteralPath $screenshot) {
Remove-Item -LiteralPath $screenshot -Force
}

$captureOption = if ($mode -eq '40') { '-exitscreenshotvicii' } else { '-exitscreenshot' }
$process = Start-X128 @(
("-{0}col" -f $mode),
'-autostart', $program,
'-autostart-warp', '-warp',
'-sounddev', 'dummy',
'-limitcycles', "$LimitCycles",
$captureOption, $screenshot
)
$process.WaitForExit()

if (-not (Test-Path -LiteralPath $screenshot)) {
throw "x128 did not create $screenshot"
}
if ((Get-Item -LiteralPath $screenshot).Length -lt 1000) {
throw "$screenshot is unexpectedly small"
}
Write-Host ("{0}-column screenshot: {1}" -f $mode, $screenshot)
}

function Invoke-MonitorVerification(
[string]$mode,
[byte]$expectedResult,
[byte]$expectedMode,
[byte]$expectedFast
) {
$transcript = Join-Path $build ("display-clock-{0}-remote.txt" -f $mode)
foreach ($path in @($transcript)) {
if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
}

$process = Start-X128 @(
("-{0}col" -f $mode),
'-autostart', $program,
'-autostart-warp', '-warp',
'-sounddev', 'dummy',
'-moncommands', $traceCommands,
'-remotemonitor',
'-remotemonitoraddress', ("ip4://127.0.0.1:{0}" -f $RemotePort)
)

$client = $null
try {
$deadline = [DateTime]::UtcNow.AddSeconds(10)
while ([DateTime]::UtcNow -lt $deadline) {
if ($process.HasExited) { throw "x128 exited before $mode-column verification" }
try {
$client = [Net.Sockets.TcpClient]::new()
$client.Connect('127.0.0.1', $RemotePort)
break
} catch {
if ($client) { $client.Dispose(); $client = $null }
Start-Sleep -Milliseconds 200
}
}
if (-not $client -or -not $client.Connected) {
throw "could not connect to the VICE remote monitor"
}

Start-Sleep -Seconds 3
$stream = $client.GetStream()
$stream.ReadTimeout = 2000
$writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true)
$writer.NewLine = "`n"
$writer.AutoFlush = $true
$writer.WriteLine('m $1d0f $1d14')
Start-Sleep -Milliseconds 300
$writer.WriteLine('m $d030 $d030')
Start-Sleep -Milliseconds 700

$buffer = New-Object byte[] 8192
$response = ""
while ($stream.DataAvailable) {
$count = $stream.Read($buffer, 0, $buffer.Length)
if ($count -le 0) { break }
$response += [Text.Encoding]::ASCII.GetString($buffer, 0, $count)
Start-Sleep -Milliseconds 50
}
[IO.File]::WriteAllText($transcript, $response, [Text.Encoding]::ASCII)

$expectedBytes = '{0:x2}\s+{1:x2}\s+{2:x2}\s+00\s+5a\s+c3' -f
$expectedResult, $expectedMode, $expectedFast
if ($response -notmatch ("(?i)C:1d0f\s+" + $expectedBytes)) {
throw "$mode-column result block mismatch; response: $response"
}
$clockMatch = [regex]::Match($response, '(?i)C:d030\s+([0-9a-f]{2})\b')
if (-not $clockMatch.Success) {
throw "$mode-column response did not include `$D030"
}
$clockValue = [Convert]::ToByte($clockMatch.Groups[1].Value, 16)
if (($clockValue -band 0x01) -ne 0) {
throw "$mode-column fallback left `$D030 speed bit set"
}

$writer.WriteLine('q')
$writer.Dispose()
$client.Dispose()
$client = $null
if (-not $process.WaitForExit(5000)) {
throw "x128 did not exit after remote-monitor quit"
}

# With a remote monitor connected, non-stopping trace messages are
# delivered in the remote transcript.
$trace = $response
if ($trace -notmatch '(?i)1c8c') {
throw "$mode-column trace did not reach fallback_done"
}
if ($mode -eq '40') {
if ($trace -notmatch '(?i)1c2e') { throw '40-column trace missed its path' }
if ($trace -match '(?i)1c49') { throw '40-column path entered fast checkpoint' }
} else {
if ($trace -notmatch '(?i)1c3d') { throw '80-column trace missed its path' }
if ($trace -notmatch '(?i)1c49') { throw '80-column trace missed fast checkpoint' }
}

Write-Host ("verified {0}-column result `${1:X2}, mode `${2:X2}, fast `${3:X2}, final clock `$00" -f
$mode, $expectedResult, $expectedMode, $expectedFast)
} finally {
if ($client) { $client.Dispose() }
if ($process -and -not $process.HasExited) {
$process.Kill()
$process.WaitForExit()
}
}
}

Invoke-Screenshot '40'
Invoke-Screenshot '80'
Invoke-MonitorVerification '40' 0x44 0x00 0x00
Invoke-MonitorVerification '80' 0x85 0x80 0x01

Write-Host 'display/clock probe passed in 40- and 80-column modes'


+ 189
- 0
newLang/scripts/verify-kernal-probe.ps1 Vedi File

@@ -0,0 +1,189 @@
# E01-S03: run the KERNAL wrapper probe from a true-drive D64 in VICE x128 and
# verify its machine-readable result block over the remote monitor.
[CmdletBinding()]
param(
[int]$RemotePort = 6512,
[int]$TimeoutSeconds = 60
)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$disk = Join-Path $build "kernal-probe.d64"
$labelsPath = Join-Path $build "kernal-probe.lbl"
$traceCommands = Join-Path $project "monitor\kernal-probe-trace.txt"
$monitorLog = Join-Path $build "kernal-probe-monitor.log"
$screenshot = Join-Path $build "kernal-probe-vicii.png"
$x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe"

& (Join-Path $PSScriptRoot "build-kernal-probe.ps1")
if ($LASTEXITCODE -ne 0) { throw "KERNAL probe build failed" }
if (-not (Test-Path -LiteralPath $x128)) { throw "x128.exe not found at $x128" }
foreach ($path in @($monitorLog, $screenshot)) {
if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force }
}

$labelText = Get-Content -Raw -LiteralPath $labelsPath
function Get-LabelAddress([string]$name) {
$match = [regex]::Match($labelText,
"(?im)^al\s+([0-9a-f]+)\s+\." + [regex]::Escape($name) + "\s*$")
if (-not $match.Success) { throw "missing label $name" }
return [Convert]::ToInt32($match.Groups[1].Value, 16)
}

$resultAddress = Get-LabelAddress "kprobe_result"
$completeAddress = Get-LabelAddress "kprobe_complete"
$stateAddress = Get-LabelAddress "kprobe_state_result"
$failAAddress = Get-LabelAddress "kprobe_fail_a"
$failPAddress = Get-LabelAddress "kprobe_fail_p"
$failStAddress = Get-LabelAddress "kprobe_fail_st"
$statusAddress = Get-LabelAddress "kprobe_status_buf"
$statusMax = 32
$blockEnd = $statusAddress + 2 * $statusMax - 1

$arguments = @(
"-drive8type", "1541", "-drive8truedrive", "-8", ('"' + $disk + '"'),
"-autostart", ('"' + $disk + '"'),
"-autostart-warp", "-warp", "-sounddev", "dummy",
"-exitscreenshotvicii", ('"' + $screenshot + '"'),
"-moncommands", ('"' + $traceCommands + '"'),
"-monlog", "-monlogname", ('"' + $monitorLog + '"'),
"-remotemonitor", "-remotemonitoraddress", ("ip4://127.0.0.1:{0}" -f $RemotePort)
) -join ' '

$psi = [Diagnostics.ProcessStartInfo]::new()
$psi.FileName = $x128
$psi.Arguments = $arguments
$psi.UseShellExecute = $true
$psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
$process = [Diagnostics.Process]::Start($psi)
$client = $null

function Read-Available($stream) {
$buffer = New-Object byte[] 16384
$text = ""
Start-Sleep -Milliseconds 300
while ($stream.DataAvailable) {
$count = $stream.Read($buffer, 0, $buffer.Length)
if ($count -le 0) { break }
$text += [Text.Encoding]::ASCII.GetString($buffer, 0, $count)
Start-Sleep -Milliseconds 50
}
return $text
}

function Convert-Dump([string]$text) {
$memory = @{}
$rows = [regex]::Matches($text, "(?i)C:([0-9a-f]{4})((?:[ ]{1,2}[0-9a-f]{2}){1,16})")
foreach ($row in $rows) {
$address = [Convert]::ToInt32($row.Groups[1].Value, 16)
foreach ($hex in ($row.Groups[2].Value.Trim() -split '\s+')) {
$memory[$address] = [Convert]::ToInt32($hex, 16)
$address++
}
}
return $memory
}

try {
$deadline = [DateTime]::UtcNow.AddSeconds(10)
while ([DateTime]::UtcNow -lt $deadline) {
if ($process.HasExited) { throw "x128 exited before remote verification" }
try {
$client = [Net.Sockets.TcpClient]::new()
$client.Connect("127.0.0.1", $RemotePort)
break
} catch {
if ($client) { $client.Dispose(); $client = $null }
Start-Sleep -Milliseconds 200
}
}
if (-not $client -or -not $client.Connected) {
throw "could not connect to VICE remote monitor"
}

$stream = $client.GetStream()
$writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true)
$writer.NewLine = "`n"
$writer.AutoFlush = $true

# True-drive loading takes several emulated seconds; poll completion.
$transcript = ""
$memory = @{}
$deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
while ($true) {
Start-Sleep -Seconds 1
$writer.WriteLine(("m `${0:X4} `${1:X4}" -f $resultAddress, $blockEnd))
$chunk = Read-Available $stream
$transcript += $chunk
$memory = Convert-Dump $chunk
if ($memory[$completeAddress] -eq 0xA5) { break }
if ([DateTime]::UtcNow -ge $deadline) {
throw "probe did not complete within $TimeoutSeconds s; last response: $chunk"
}
$writer.WriteLine("x")
$transcript += Read-Available $stream
}

# Let the post-done summary finish printing; x128 writes the VIC-II
# screenshot when the monitor quits it below.
$writer.WriteLine("x")
Start-Sleep -Seconds 2
$writer.WriteLine("m 0000 0000")
$transcript += Read-Available $stream
[IO.File]::WriteAllText((Join-Path $build "kernal-probe-remote.txt"), $transcript)

$failures = @()
function Expect([string]$name, [int]$address, [int]$value) {
if ($memory[$address] -ne $value) {
$script:failures += ("{0} at `${1:X4} = `${2:X2}, expected `${3:X2}" -f
$name, $address, $memory[$address], $value)
}
}
Expect "result" $resultAddress 0xFF
Expect "complete" $completeAddress 0xA5
foreach ($state in 0, 1) {
Expect "state$state result" ($stateAddress + $state) 0xFF
Expect "state$state CHKIN error" ($failAAddress + $state) 0x05
if (($memory[$failPAddress + $state] -band 0x01) -ne 0x01) {
$failures += "state$state CHKIN carry was clear"
}
if (($memory[$failStAddress + $state] -band 0x80) -ne 0x80) {
$failures += ("state{0} READST `${1:X2} lacks device-not-present bit 7" -f
$state, $memory[$failStAddress + $state])
}
}

$statusText = @()
foreach ($state in 0, 1) {
$chars = @()
for ($i = 0; $i -lt $statusMax; $i++) {
$byte = $memory[$statusAddress + $state * $statusMax + $i]
if ($byte -eq 0x0D) { break }
$chars += [char]$byte
}
$statusText += (-join $chars)
}

$writer.WriteLine("quit")
$writer.Dispose()
$client.Dispose()
$client = $null
if (-not $process.WaitForExit(10000)) { throw "x128 did not exit after monitor quit" }

if ($failures.Count -gt 0) {
throw ("KERNAL probe verification failed:`n " + ($failures -join "`n "))
}

Write-Host ("verified state results `$FF/`$FF, CHKIN(30) error 5 with C=1, " +
"READST `${0:X2}/`${1:X2}" -f $memory[$failStAddress], $memory[$failStAddress + 1])
Write-Host ("drive 8 status from bank 0: '{0}'" -f $statusText[0])
Write-Host ("drive 8 status from bank 1: '{0}'" -f $statusText[1])
if (Test-Path -LiteralPath $screenshot) { Write-Host ("screenshot: {0}" -f $screenshot) }
} finally {
if ($client) { $client.Dispose() }
if ($process -and -not $process.HasExited) {
$process.Kill()
$process.WaitForExit()
}
}

+ 111
- 0
newLang/scripts/verify-minimal.ps1 Vedi File

@@ -0,0 +1,111 @@
# E01-S01: execute the platform proof and verify entry, completion, and result.
[CmdletBinding()]
param(
[int]$RemotePort = 6510
)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$program = Join-Path $build "minimal.prg"
$traceCommands = Join-Path $project "monitor\minimal-trace.txt"
$monitorLog = Join-Path $build "minimal-monitor.log"
$x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe"

& (Join-Path $PSScriptRoot "build-minimal.ps1")
if ($LASTEXITCODE -ne 0) { throw "minimal build failed" }
if (-not (Test-Path -LiteralPath $x128)) { throw "x128.exe not found at $x128" }
if (Test-Path -LiteralPath $monitorLog) {
Remove-Item -LiteralPath $monitorLog -Force
}

$arguments = @(
"-autostart", ('"' + $program + '"'),
"-autostart-warp", "-warp",
"-sounddev", "dummy",
"-moncommands", ('"' + $traceCommands + '"'),
"-monlog", "-monlogname", ('"' + $monitorLog + '"'),
"-remotemonitor",
"-remotemonitoraddress", ("ip4://127.0.0.1:{0}" -f $RemotePort)
) -join ' '

$psi = [Diagnostics.ProcessStartInfo]::new()
$psi.FileName = $x128
$psi.Arguments = $arguments
$psi.UseShellExecute = $true
$psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
$process = [Diagnostics.Process]::Start($psi)
$client = $null

try {
$deadline = [DateTime]::UtcNow.AddSeconds(10)
while ([DateTime]::UtcNow -lt $deadline) {
if ($process.HasExited) { throw "x128 exited before remote verification" }
try {
$client = [Net.Sockets.TcpClient]::new()
$client.Connect("127.0.0.1", $RemotePort)
break
} catch {
if ($client) { $client.Dispose(); $client = $null }
Start-Sleep -Milliseconds 200
}
}
if (-not $client -or -not $client.Connected) {
throw "could not connect to the VICE remote monitor"
}

# Autostart runs in warp. Give BASIC enough wall time to load, RUN, execute
# the message loop, and return before inspecting the result byte.
Start-Sleep -Seconds 3

$stream = $client.GetStream()
$stream.ReadTimeout = 2000
$writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true)
$writer.NewLine = "`n"
$writer.AutoFlush = $true
$writer.WriteLine('m $1c3e $1c3e')
Start-Sleep -Milliseconds 300

$buffer = New-Object byte[] 8192
$response = ""
while ($stream.DataAvailable) {
$count = $stream.Read($buffer, 0, $buffer.Length)
if ($count -le 0) { break }
$response += [Text.Encoding]::ASCII.GetString($buffer, 0, $count)
Start-Sleep -Milliseconds 50
}

if ($response -notmatch '(?i)C:1c3e\s+a5\b') {
throw "minimal_result was not `$A5; monitor response: $response"
}

$writer.WriteLine("q")
$writer.Dispose()
$client.Dispose()
$client = $null

if (-not $process.WaitForExit(5000)) {
throw "x128 did not exit after remote-monitor quit"
}

if (-not (Test-Path -LiteralPath $monitorLog)) {
throw "VICE did not create the monitor trace log"
}
$trace = Get-Content -Raw -LiteralPath $monitorLog
if ($trace -notmatch '(?i)1c0d') {
throw "monitor trace did not record minimal_entry at `$1C0D"
}
if ($trace -notmatch '(?i)1c24') {
throw "monitor trace did not record minimal_done at `$1C24"
}

Write-Host "verified x128 entry `$1C0D, done `$1C24, result `$A5"
Write-Host ("monitor log: {0}" -f $monitorLog)
} finally {
if ($client) { $client.Dispose() }
if ($process -and -not $process.HasExited) {
$process.Kill()
$process.WaitForExit()
}
}


+ 188
- 0
newLang/scripts/verify-mmu-probe.ps1 Vedi File

@@ -0,0 +1,188 @@
# E01-S02: execute the MMU probe and verify its machine-readable result block.
[CmdletBinding()]
param([int]$RemotePort = 6511)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
$build = Join-Path $project "build"
$program = Join-Path $build "mmu-probe.prg"
$labelsPath = Join-Path $build "mmu-probe.lbl"
$traceCommands = Join-Path $project "monitor\mmu-probe-trace.txt"
$monitorLog = Join-Path $build "mmu-probe-monitor.log"
$x128 = "C:\Program Files\GTK3VICE-3.10-win64\bin\x128.exe"

& (Join-Path $PSScriptRoot "build-mmu-probe.ps1")
if ($LASTEXITCODE -ne 0) { throw "MMU probe build failed" }
if (Test-Path -LiteralPath $monitorLog) { Remove-Item -LiteralPath $monitorLog -Force }

$labelText = Get-Content -Raw -LiteralPath $labelsPath
function Get-LabelAddress([string]$name) {
$match = [regex]::Match($labelText,
"(?im)^al\s+([0-9a-f]+)\s+\." + [regex]::Escape($name) + "\s*$")
if (-not $match.Success) { throw "missing label $name" }
return [Convert]::ToInt32($match.Groups[1].Value, 16)
}

$resultAddress = Get-LabelAddress "mmu_probe_result"
$completeAddress = Get-LabelAddress "mmu_probe_complete"
$bank0Address = Get-LabelAddress "mmu_probe_observed_bank0"
$bank1Address = Get-LabelAddress "mmu_probe_observed_bank1"
$p0Address = Get-LabelAddress "mmu_probe_observed_p0"
$p1Address = Get-LabelAddress "mmu_probe_observed_p1"
$p0cBank0Address = Get-LabelAddress "mmu_probe_obs_p0c_b0"
$p1cBank0Address = Get-LabelAddress "mmu_probe_obs_p1c_b0"
$stateEndAddress = Get-LabelAddress "mmu_probe_common_rcr"
$entryAddress = Get-LabelAddress "mmu_probe_entry"
$doneAddress = Get-LabelAddress "mmu_probe_done"
$codeEndAddress = Get-LabelAddress "mmu_probe_code_end"

# The checked-in trace file has the fixed entry point; the done address moves
# with the code, so append it from the fresh label file.
$generatedTrace = Join-Path $build "mmu-probe-trace.gen.txt"
$traceText = (Get-Content -Raw -LiteralPath $traceCommands) +
("trace exec `${0:X4}`ncommand 4 `"disable 2`"`n" -f $doneAddress)
[IO.File]::WriteAllText($generatedTrace, $traceText, [Text.Encoding]::ASCII)
$traceCommands = $generatedTrace

$arguments = @(
"-autostart", ('"' + $program + '"'),
"-autostart-warp", "-warp", "-sounddev", "dummy",
"-moncommands", ('"' + $traceCommands + '"'),
"-monlog", "-monlogname", ('"' + $monitorLog + '"'),
"-remotemonitor", "-remotemonitoraddress", ("ip4://127.0.0.1:{0}" -f $RemotePort)
) -join ' '

$psi = [Diagnostics.ProcessStartInfo]::new()
$psi.FileName = $x128
$psi.Arguments = $arguments
$psi.UseShellExecute = $true
$psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
$process = [Diagnostics.Process]::Start($psi)
$client = $null

try {
$deadline = [DateTime]::UtcNow.AddSeconds(10)
while ([DateTime]::UtcNow -lt $deadline) {
if ($process.HasExited) { throw "x128 exited before remote verification" }
try {
$client = [Net.Sockets.TcpClient]::new()
$client.Connect("127.0.0.1", $RemotePort)
break
} catch {
if ($client) { $client.Dispose(); $client = $null }
Start-Sleep -Milliseconds 200
}
}
if (-not $client -or -not $client.Connected) {
throw "could not connect to VICE remote monitor"
}

Start-Sleep -Seconds 3
$stream = $client.GetStream()
$stream.ReadTimeout = 2000
$writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true)
$writer.NewLine = "`n"
$writer.AutoFlush = $true
$writer.WriteLine(("m `${0:X4} `${1:X4}" -f $resultAddress, $stateEndAddress))
Start-Sleep -Milliseconds 400

$buffer = New-Object byte[] 16384
$response = ""
while ($stream.DataAvailable) {
$count = $stream.Read($buffer, 0, $buffer.Length)
if ($count -le 0) { break }
$response += [Text.Encoding]::ASCII.GetString($buffer, 0, $count)
Start-Sleep -Milliseconds 50
}

$expected = @(
@{ Address = $resultAddress; Value = 0xFF; Name = "result" },
@{ Address = $completeAddress; Value = 0xA5; Name = "complete" },
@{ Address = $bank0Address; Value = 0x11; Name = "bank0" },
@{ Address = $bank1Address; Value = 0x22; Name = "bank1" },
@{ Address = $p0Address; Value = 0x77; Name = "page0" },
@{ Address = $p1Address; Value = 0x33; Name = "page1" },
@{ Address = $p0cBank0Address; Value = 0x66; Name = "page0-common-bank0" },
@{ Address = $p1cBank0Address; Value = 0x5A; Name = "page1-common-bank0" }
)
# Parse every monitor dump row into address -> byte so checks do not
# depend on where a 16-byte row happens to start.
$memory = @{}
$rows = [regex]::Matches($response,
"(?i)C:([0-9a-f]{4})((?:[ ]{1,2}[0-9a-f]{2}){1,16})")
foreach ($row in $rows) {
$address = [Convert]::ToInt32($row.Groups[1].Value, 16)
foreach ($hex in ($row.Groups[2].Value.Trim() -split '\s+')) {
$memory[$address] = [Convert]::ToInt32($hex, 16)
$address++
}
}
foreach ($item in $expected) {
if (-not $memory.ContainsKey($item.Address) -or
$memory[$item.Address] -ne $item.Value) {
throw ("{0} at `${1:X4} was not `${2:X2}; monitor response: {3}" -f
$item.Name, $item.Address, $item.Value, $response)
}
}

$writer.WriteLine("q")
$writer.Dispose()
$client.Dispose()
$client = $null
if (-not $process.WaitForExit(5000)) { throw "x128 did not exit after monitor quit" }

if (-not (Test-Path -LiteralPath $monitorLog)) { throw "monitor log not created" }
# Trace hits go to the log before the remote client connects and to the
# remote stream afterwards; examine both.
$trace = (Get-Content -Raw -LiteralPath $monitorLog) + "`n" + $response
[IO.File]::WriteAllText((Join-Path $build "mmu-probe-trace-dump.txt"), $trace)

# Every hit is "#n (Trace <kind> <addr>) ..." followed by ".C:<pc> ...".
$hits = [regex]::Matches($trace,
"(?im)^#\d+ \(Trace\s+(exec|store) ([0-9a-f]{4})\)[^\r\n]*\r?\n\.C:([0-9a-f]{4})")
$inWindow = $false
$windowClosed = $false
$storeCount = 0
$written = @{}
foreach ($hit in $hits) {
$kind = $hit.Groups[1].Value.ToLowerInvariant()
$target = [Convert]::ToInt32($hit.Groups[2].Value, 16)
$pc = [Convert]::ToInt32($hit.Groups[3].Value, 16)
if ($kind -eq 'exec') {
if ($target -eq $entryAddress -and -not $windowClosed) { $inWindow = $true }
elseif ($target -eq $doneAddress -and $inWindow) {
$inWindow = $false; $windowClosed = $true; break
}
continue
}
if (-not $inWindow) { continue }
if ($pc -lt $entryAddress -or $pc -ge $codeEndAddress) {
throw ("MMU store to `${0:X4} from `${1:X4}, outside probe code" -f $target, $pc)
}
$written[$target] = $true
$storeCount++
}
if (-not $windowClosed) { throw "trace has no complete entry..done probe window" }
foreach ($register in @(0xFF00, 0xD506, 0xD507, 0xD508, 0xD509, 0xD50A)) {
if (-not $written.ContainsKey($register)) {
throw ("probe window has no store to `${0:X4}" -f $register)
}
}
foreach ($register in @(0xD500, 0xD501, 0xD502, 0xD503, 0xD504, 0xD505, 0xD50B)) {
if ($written.ContainsKey($register)) {
throw ("probe wrote unintended MMU register `${0:X4}" -f $register)
}
}
Write-Host ("verified {0} MMU stores in the probe window, all from probe code" -f $storeCount)

Write-Host ("verified MMU result `$FF, completion `$A5, banks `$11/`$22, " +
"pages `$77/`$33, common-priority bank0 `$66/`$5A")
Write-Host ("monitor log: {0}" -f $monitorLog)
} finally {
if ($client) { $client.Dispose() }
if ($process -and -not $process.HasExited) {
$process.Kill()
$process.WaitForExit()
}
}


+ 166
- 0
newLang/src/platform/display-clock.s Vedi File

@@ -0,0 +1,166 @@
; E01-S04: C128 display/clock behavior probe.
;
; The program is loaded as BASIC 7 text at $1C01 and entered with SYS 7181.
; It deliberately leaves display selection to the C128 editor. MODE ($D7)
; bit 7 reports the active display: clear = VIC 40-column, set = VDC
; 80-column. KERNAL CHROUT therefore targets whichever editor is active.
;
; The C128 Programmer's Reference Guide documents VIC register $D030 bit 0:
; set = 2 MHz with VIC video disabled, clear = 1 MHz with VIC video enabled.
; This probe selects 2 MHz only for an active 80-column display, exercises
; CHROUT while fast (I/O clock stretching is hardware-defined), then restores
; 1 MHz before returning to BASIC. It never accesses VDC $D600/$D601 and keeps
; all machine-readable state in ordinary program RAM.

.setcpu "6502"

MODE = $00D7
CLOCK = $D030
CHROUT = $FFD2

.export display_clock_entry
.export mode_40_checkpoint
.export mode_80_checkpoint
.export fast_checkpoint
.export fallback_done
.export display_result
.export mode_observed
.export fast_observed
.export final_clock
.export semantic_a
.export semantic_b

.segment "LOADADDR"
.word $1C01

.segment "EXEHDR"
.word basic_end
.word 10
.byte $9E ; BASIC SYS token
.byte "7181" ; entry at $1C0D
.byte $00
basic_end:
.word $0000

.segment "CODE"
display_clock_entry:
lda MODE
sta mode_observed

; Deterministic safe baseline: bit 1 is a chip-test facility and must
; be clear; bit 0 clear selects 1 MHz and restores VIC visibility.
lda CLOCK
and #$FC
sta CLOCK

lda #$00
sta display_result
sta fast_observed
sta final_clock

lda MODE
bmi mode_80

mode_40:
lda #$40 ; result bit 6 = 40-column path
sta display_result
mode_40_checkpoint:
ldx #<message_40
ldy #>message_40
jsr print_message
jmp verify_semantic_ram

mode_80:
lda #$80 ; result bit 7 = 80-column path
sta display_result
mode_80_checkpoint:
ldx #<message_80
ldy #>message_80
jsr print_message

; Fast mode is safe for the VDC display. Preserve normal/test bits as
; zero and set only the documented speed bit.
lda #$01
sta CLOCK
fast_checkpoint:
lda CLOCK
and #$01
sta fast_observed
beq fast_message
lda display_result
ora #$01 ; result bit 0 = fast bit observed set
sta display_result
fast_message:
ldx #<message_fast
ldy #>message_fast
jsr print_message ; KERNAL I/O while CPU fast

; Always return the machine to the conservative fallback.
lda #$00
sta CLOCK
ldx #<message_restore
ldy #>message_restore
jsr print_message

verify_semantic_ram:
lda semantic_a
cmp #$5A
bne semantic_done
lda semantic_b
cmp #$C3
bne semantic_done
lda display_result
ora #$04 ; result bit 2 = ordinary RAM survived
sta display_result
semantic_done:
lda CLOCK
and #$01
sta final_clock
fallback_done:
rts

; X/Y = little-endian pointer to a zero-terminated PETSCII string. The load
; operand is self-modified in program RAM, avoiding undocumented zero-page
; assumptions in this pre-memory-map probe.
print_message:
stx message_load + 1
sty message_load + 2
print_next:
message_load:
lda $FFFF
beq print_done
jsr CHROUT
inc message_load + 1
bne print_next
inc message_load + 2
bne print_next
print_done:
rts

.segment "RODATA"
message_40:
.byte $0D
.byte "NOVA128 40 COL KERNAL OK", $0D
.byte "1MHZ FALLBACK OK", $0D, $00
message_80:
.byte $0D
.byte "NOVA128 80 COL KERNAL OK", $0D, $00
message_fast:
.byte "2MHZ KERNAL IO OK", $0D, $00
message_restore:
.byte "RESTORED 1MHZ", $0D, $00

.segment "DATA"
display_result:
.byte $00
mode_observed:
.byte $00
fast_observed:
.byte $00
final_clock:
.byte $00
semantic_a:
.byte $5A
semantic_b:
.byte $C3


+ 507
- 0
newLang/src/platform/kernal-probe.s Vedi File

@@ -0,0 +1,507 @@
; E01-S03: C128 KERNAL mapping-wrapper probe.
;
; The probe enters two "compiler mappings" that match the E01-S02 findings:
; state 0: CR=$0E RAM bank 0 to $BFFF, system ROM high, I/O
; state 1: CR=$4E the same with RAM bank 1
; both with 8K bottom common RAM (RCR low bits %0110). Compiler mappings run
; with IRQs masked (see docs/mmu-probe.md).
;
; kcall is the wrapper contract under test:
; in: kc_target = KERNAL entry; A/X/Y = routine arguments
; out: A/X/Y and N/Z/C exactly as the routine returned them; the I flag is
; the caller's; CR/RCR/P0/P1 equal their values at entry
; uses: kc_* variables; hardware stack: the caller's JSR plus one more
; return address (kc_jump) on top of the routine's own usage
; Inside the call the KERNAL sees the mapping BASIC had when the probe began
; (CR/RCR saved at entry) with IRQs enabled. The wrapper and every byte it
; touches live below $2000, i.e. in bottom common RAM, so it runs unchanged
; from either bank.
;
; From each state the probe prints a line through CHROUT, reads the drive-8
; error channel (success path), and attempts to read from absent device 30
; (failure path), closing channels on both paths. A per-state result byte of
; $FF means every check passed; see docs/kernal-wrapper.md for the bits.

.setcpu "6502"

SETBNK = $FF68
READST = $FFB7
SETLFS = $FFBA
SETNAM = $FFBD
OPEN = $FFC0
CLOSE = $FFC3
CHKIN = $FFC6
CLRCHN = $FFCC
CHRIN = $FFCF
CHROUT = $FFD2

LDTND = $98 ; number of open logical files

MMU_CR = $FF00
MMU_RCR = $D506 ; RCR..P1H are $D506..$D50A

RCR_COMMON_8K_BOTTOM = $06
STATUS_MAX = 32 ; bytes per status buffer
FAIL_DEVICE = 30 ; no device answers here in VICE

BIT_CHROUT = $01 ; every CHROUT returned carry clear
BIT_DISK_OPEN = $02 ; OPEN + CHKIN on 8,15 returned carry clear
BIT_STATUS = $04 ; status text is "dd...<CR>"
BIT_CLOSED_OK = $08 ; LDTND = 0 after the success path
BIT_FAIL_ERR = $10 ; CHKIN on device 30: C=1, A=5
BIT_CLOSED_ER = $20 ; LDTND = 0 after the failure path
BIT_MMU_SAME = $40 ; CR/RCR/P0/P1 identical after every kcall
BIT_BANK_SAME = $80 ; $4000 still shows this bank's marker

.macro KCALL addr
jsr kcall_inline ; A/X/Y pass through untouched
.word addr
.endmacro

.export kprobe_entry
.export kprobe_done
.export kprobe_result
.export kprobe_complete
.export kprobe_state_result
.export kprobe_fail_a
.export kprobe_fail_p
.export kprobe_fail_st
.export kprobe_status_buf
.export kprobe_code_end
.export kcall

.segment "LOADADDR"
.word $1C01

.segment "EXEHDR"
.word basic_end
.word 10
.byte $9E
.byte "7181" ; SYS $1C0D
.byte $00
basic_end:
.word $0000

.segment "CODE"
kprobe_entry:
php
sei
lda #$00
sta kprobe_result
sta kprobe_complete

lda MMU_CR
sta entry_mmu
ldx #4
@save: lda MMU_RCR,x
sta entry_mmu+1,x
dex
bpl @save

lda entry_mmu ; KERNAL calls use BASIC's mapping
sta kc_kernal_cr
lda entry_mmu+1
sta kc_kernal_rcr
and #$C0 ; keep VIC bank bits
ora #RCR_COMMON_8K_BOTTOM
sta compiler_rcr

lda #$00
sta state_idx
jsr run_state
inc state_idx
jsr run_state

; run_state left BASIC's mapping in place; confirm it exactly.
lda #$FF
sta kprobe_result
lda MMU_CR
cmp entry_mmu
bne @bad
ldx #4
@chk: lda MMU_RCR,x
cmp entry_mmu+1,x
bne @bad
dex
bpl @chk
lda kprobe_state_result
and kprobe_state_result+1
cmp #$FF
beq @good
@bad: lda #$00
sta kprobe_result
@good:
lda #$A5
sta kprobe_complete
kprobe_done:
plp

; Default mapping again: print what each state read from drive 8.
ldx #$00
jsr print_status
ldx #STATUS_MAX
jsr print_status
ldy #ok_message - messages
lda kprobe_result
cmp #$FF
beq @msg
ldy #fail_message - messages
@msg: jsr print_message
rts

; ---------------------------------------------------------------- one state
run_state:
lda #$FF
sta mmu_ok
lda #$00
sta result_bits

ldx state_idx
lda compiler_rcr ; bottom common first, then the bank
sta MMU_RCR
lda state_cr,x
sta MMU_CR

lda MMU_CR
sta exp_mmu
ldx #4
@snap: lda MMU_RCR,x
sta exp_mmu+1,x
dex
bpl @snap

ldx state_idx
lda $4000
sta saved_4000
lda bank_marker,x
sta $4000

; ------------------------------------------- CHROUT from this state
lda #BIT_CHROUT
sta chrout_ok
ldy #state_message - messages
@out: lda messages,y
beq @digit
sty msg_y
KCALL CHROUT
bcc :+
lda #$00
sta chrout_ok
: ldy msg_y
iny
bne @out
@digit: lda state_idx
ora #'0'
KCALL CHROUT
lda #$0D
KCALL CHROUT
lda result_bits
ora chrout_ok
sta result_bits

; ------------------------------------- success path: drive 8 status
lda #$00
tax
KCALL SETBNK ; filename/data bank 0 (unused: no name)
lda #15
ldx #8
ldy #15
KCALL SETLFS
lda #$00
KCALL SETNAM
KCALL OPEN
bcs @close_ok
ldx #15
KCALL CHKIN
bcs @close_ok
lda result_bits
ora #BIT_DISK_OPEN
sta result_bits

ldx state_idx
lda status_base,x
sta buf_pos
clc
adc #STATUS_MAX
sta buf_end
@read: KCALL CHRIN
ldx buf_pos
sta kprobe_status_buf,x
inc buf_pos
cmp #$0D
beq @read_done
inx
inx
cpx buf_end ; leave room for the terminating CR
bcc @read
lda #$0D
ldx buf_pos
sta kprobe_status_buf,x
jmp @close_ok ; truncated: no BIT_STATUS
@read_done:
ldx state_idx ; status must start with two digits
ldy status_base,x
lda kprobe_status_buf,y
jsr is_digit
bcc @close_ok
lda kprobe_status_buf+1,y
jsr is_digit
bcc @close_ok
lda result_bits
ora #BIT_STATUS
sta result_bits
@close_ok:
KCALL CLRCHN
lda #15
KCALL CLOSE
lda LDTND
bne :+
lda result_bits
ora #BIT_CLOSED_OK
sta result_bits
:
; ------------------------------------ failure path: absent device
lda #14
ldx #FAIL_DEVICE
ldy #15
KCALL SETLFS
lda #$00
KCALL SETNAM
KCALL OPEN ; no name: nothing is sent, so C=0
ldx #14
KCALL CHKIN ; TALK reaches nobody
sta tmp_a
php
pla
ldx state_idx
sta kprobe_fail_p,x
lda tmp_a
sta kprobe_fail_a,x
KCALL READST
ldx state_idx
sta kprobe_fail_st,x
lda kprobe_fail_p,x
lsr a ; C flag of CHKIN
bcc @fail_close
lda kprobe_fail_a,x
cmp #5 ; DEVICE NOT PRESENT
bne @fail_close
lda result_bits
ora #BIT_FAIL_ERR
sta result_bits
@fail_close:
KCALL CLRCHN
lda #14
KCALL CLOSE
lda LDTND
bne :+
lda result_bits
ora #BIT_CLOSED_ER
sta result_bits
:
; ------------------------------------------------ mapping integrity
ldx state_idx
lda $4000
cmp bank_marker,x
bne :+
lda result_bits
ora #BIT_BANK_SAME
sta result_bits
: lda saved_4000
sta $4000
lda mmu_ok
beq :+
lda result_bits
ora #BIT_MMU_SAME
sta result_bits
: lda result_bits
sta kprobe_state_result,x

lda kc_kernal_cr ; bank first: this code is in bank 0
sta MMU_CR
lda kc_kernal_rcr
sta MMU_RCR
rts

; C=1 when A is an ASCII/PETSCII digit.
is_digit:
cmp #'0'
bcc @no
cmp #'9'+1
bcs @no
sec
rts
@no: clc
rts

; Probe-only: read the inline target word after the JSR, step the return
; address over it, then behave exactly like kcall_checked. Preserves A/X/Y.
kcall_inline:
sta inl_a
sty inl_y
pla
sta @src+1 ; return address = JSR's last byte
pla
sta @src+2
ldy #1
@src: lda $FFFF,y ; patched above
sta kc_target-1,y
iny
cpy #3
bne @src
lda @src+1 ; resume after the inline word
clc
adc #2
tay
lda @src+2
adc #0
pha
tya
pha
ldy inl_y
lda inl_a
; fall through
; Probe-only: kcall followed by an exact MMU comparison against exp_mmu.
kcall_checked:
jsr kcall
php
pha
stx chk_x
lda MMU_CR
cmp exp_mmu
bne @bad
ldx #4
@cmp: lda MMU_RCR,x
cmp exp_mmu+1,x
bne @bad
dex
bpl @cmp
bmi @done
@bad: lda #$00
sta mmu_ok
@done: ldx chk_x
pla
plp
rts

; ------------------------------------------------------- the wrapper itself
kcall:
php
sei
sta kc_a
pla
sta kc_caller_p
lda MMU_CR
sta kc_saved_cr
lda MMU_RCR
sta kc_saved_rcr
lda kc_kernal_cr ; bank 0 first: kcall lives in bank 0
sta MMU_CR
lda kc_kernal_rcr
sta MMU_RCR
cli ; KERNAL mapping is the default one
lda kc_a
jsr kc_jump
php
sei
sta kc_a
pla
sta kc_result_p
lda kc_saved_rcr ; common first, then the compiler bank
sta MMU_RCR
lda kc_saved_cr
sta MMU_CR
lda kc_caller_p ; result N/V/Z/C with the caller's I flag
and #$04
sta kc_i_bit
lda kc_result_p
and #$FB
ora kc_i_bit
pha
lda kc_a
plp
rts
kc_jump:
jmp (kc_target)
.assert <kc_target <> $FF, error, "kc_target must not straddle a page (JMP bug)"

; ------------------------------------------- default-mapping print helpers
print_message:
lda messages,y
beq @done
sty print_y
jsr CHROUT
ldy print_y
iny
bne print_message
@done: rts

print_status:
stx print_x
ldy #drive_message - messages
jsr print_message
ldx print_x
@loop: lda kprobe_status_buf,x
sta print_ch
stx print_x
jsr CHROUT
ldx print_x
lda print_ch
cmp #$0D
beq @done
inx
bne @loop
@done: rts
kprobe_code_end:

.segment "RODATA"
state_cr: .byte $0E, $4E
bank_marker: .byte $B0, $B1
status_base: .byte 0, STATUS_MAX
messages:
state_message:
.byte "KCALL CHROUT STATE ", $00
drive_message:
.byte "DRIVE 8: ", $00
ok_message:
.byte "NOVA128 KERNAL PROBE OK", $0D, $00
fail_message:
.byte "NOVA128 KERNAL PROBE FAIL", $0D, $00

.segment "DATA"
kprobe_result: .byte $00
kprobe_complete: .byte $00
kprobe_state_result: .byte $00, $00
kprobe_fail_a: .byte $00, $00
kprobe_fail_p: .byte $00, $00
kprobe_fail_st: .byte $00, $00
kprobe_status_buf: .res STATUS_MAX * 2, $00

entry_mmu: .res 6, $00 ; CR, RCR, P0L, P0H, P1L, P1H
exp_mmu: .res 6, $00
compiler_rcr: .byte $00
state_idx: .byte $00
result_bits: .byte $00
chrout_ok: .byte $00
mmu_ok: .byte $00
saved_4000: .byte $00
msg_y: .byte $00
buf_pos: .byte $00
buf_end: .byte $00
tmp_a: .byte $00
chk_x: .byte $00
inl_a: .byte $00
inl_y: .byte $00
print_x: .byte $00
print_y: .byte $00
print_ch: .byte $00

kc_target: .word $0000
kc_kernal_cr: .byte $00
kc_kernal_rcr: .byte $00
kc_saved_cr: .byte $00
kc_saved_rcr: .byte $00
kc_caller_p: .byte $00
kc_result_p: .byte $00
kc_i_bit: .byte $00
kc_a: .byte $00

+ 60
- 0
newLang/src/platform/minimal.s Vedi File

@@ -0,0 +1,60 @@
; E01-S01: smallest C128-native Nova128 platform proof.
;
; Loaded as a BASIC 7 program at $1C01. RUN executes SYS 7181, which enters
; `minimal_entry` at $1C0D. The routine uses the documented KERNAL CHROUT jump
; table while the normal C128 BASIC mapping is active, records $A5 in
; `minimal_result`, and returns to BASIC with RTS.
;
; This file deliberately does not change the MMU or CPU clock. Those behaviors
; belong to E01-S02 and E01-S04.

.setcpu "6502"

CHROUT = $FFD2

.export minimal_entry
.export minimal_done
.export minimal_result

.segment "LOADADDR"
.word $1C01

.segment "EXEHDR"
.word basic_end ; link to BASIC end marker
.word 10 ; BASIC line 10
.byte $9E ; SYS token
.byte "7181" ; $1C0D, first byte after the header
.byte $00 ; end of BASIC line
basic_end:
.word $0000 ; end of BASIC program

.segment "CODE"
minimal_entry:
lda #$00
sta minimal_result
ldx #$00

print_loop:
lda minimal_message,x
beq print_complete
jsr CHROUT
inx
bne print_loop ; message is deliberately shorter than 256

print_complete:
lda #$A5
sta minimal_result

minimal_done:
rts ; return from BASIC's SYS call

.segment "RODATA"
minimal_message:
.byte $0D
.byte "NOVA128 C128 NATIVE OK"
.byte $0D, $00

.segment "DATA"
minimal_result:
.byte $00


+ 505
- 0
newLang/src/platform/mmu-probe.s Vedi File

@@ -0,0 +1,505 @@
; E01-S02: C128 MMU, common-RAM, and page-pointer platform proof.
;
; The probe starts under BASIC 7 at $1C0D. It temporarily configures 8K common
; RAM at both bottom and top, so its code/data, zero page, and hardware stack
; remain in bank 0 while CR bit 6 selects RAM bank 1. KERNAL ROM and I/O remain
; mapped in both probe configurations ($0E/$4E).
;
; IRQ state is saved with PHP, IRQs are disabled with SEI for every MMU/page
; pointer transition, all MMU state is restored, and PLP restores the caller's
; interrupt flag before KERNAL output. This probe does not define the final
; compiler map and does not implement the E01-S03 KERNAL wrapper contract.

.setcpu "6502"

CHROUT = $FFD2
MMU_CR = $FF00 ; always visible configuration register
MMU_RCR = $D506 ; I/O-visible RAM/common configuration
MMU_P0L = $D507
MMU_P0H = $D508
MMU_P1L = $D509
MMU_P1H = $D50A

CR_BANK0_RAM_KERNAL_IO = $0E ; RAM $4000-$BFFF, KERNAL high, I/O
CR_BANK1_RAM_KERNAL_IO = $4E
RCR_COMMON_8K_BOTH = $0E ; 8K common, bottom + top

BIT_BANK0 = $01
BIT_BANK1 = $02
BIT_COMMON = $04
BIT_RETURN = $08
BIT_P0_RELOC = $10
BIT_P0_COMMON = $20
BIT_P1_COMMON = $40
BIT_RESTORED = $80

.export mmu_probe_entry
.export mmu_probe_done
.export mmu_probe_result
.export mmu_probe_complete
.export mmu_probe_observed_bank0
.export mmu_probe_observed_bank1
.export mmu_probe_observed_p0
.export mmu_probe_observed_p1
.export mmu_probe_saved_cr
.export mmu_probe_saved_rcr
.export mmu_probe_saved_p0l
.export mmu_probe_saved_p0h
.export mmu_probe_saved_p1l
.export mmu_probe_saved_p1h
.export mmu_probe_common_rcr
.export mmu_probe_code_end
.export mmu_probe_obs_p0c_zp
.export mmu_probe_obs_p0c_b0
.export mmu_probe_obs_p0c_b1
.export mmu_probe_obs_p1c_stack
.export mmu_probe_obs_p1c_b0

.segment "LOADADDR"
.word $1C01

.segment "EXEHDR"
.word basic_end
.word 10
.byte $9E
.byte "7181" ; SYS $1C0D
.byte $00
basic_end:
.word $0000

.segment "CODE"
mmu_probe_entry:
php ; preserve BASIC's interrupt state
sei ; no IRQ may observe a temporary mapping

lda #$00
sta mmu_probe_result
sta mmu_probe_complete
sta common_stage
sta return_marker

lda MMU_CR
sta saved_cr
lda MMU_RCR
sta saved_rcr
lda MMU_P0L
sta saved_p0l
lda MMU_P0H
sta saved_p0h
lda MMU_P1L
sta saved_p1l
lda MMU_P1H
sta saved_p1h

tsx
stx saved_sp

; Preserve the VIC bank while selecting 8K common at top and bottom.
lda saved_rcr
and #$C0
ora #RCR_COMMON_8K_BOTH
sta probe_rcr_common
and #$C0 ; same VIC bank, no common RAM
sta probe_rcr_none

lda probe_rcr_common
sta MMU_RCR

; ------------------------------------------------ distinct banks
lda #CR_BANK0_RAM_KERNAL_IO
sta MMU_CR
lda $4000
sta saved_bank0_byte
lda #$11
sta $4000

lda #$3C
sta common_marker

lda #CR_BANK1_RAM_KERNAL_IO
sta MMU_CR
lda $4000
sta saved_bank1_byte
lda #$22
sta $4000
lda $4000
sta mmu_probe_observed_bank1
cmp #$22
bne bank1_bad
lda mmu_probe_result
ora #BIT_BANK1
sta mmu_probe_result
bank1_bad:

lda common_marker
cmp #$3C
bne common_bank1_bad
inc common_stage
lda #$C3
sta common_marker
common_bank1_bad:

; A JSR/RTS while bank 1 is selected proves code and hardware stack are
; safely visible through bottom common RAM.
jsr bank1_return_probe
tsx
cpx saved_sp
bne bank_return_bad
lda return_marker
cmp #$5A
bne bank_return_bad
lda mmu_probe_result
ora #BIT_RETURN
sta mmu_probe_result
bank_return_bad:

lda #CR_BANK0_RAM_KERNAL_IO
sta MMU_CR
lda $4000
sta mmu_probe_observed_bank0
cmp #$11
bne bank0_bad
lda mmu_probe_result
ora #BIT_BANK0
sta mmu_probe_result
bank0_bad:

lda common_stage
cmp #$01
bne common_bad
lda common_marker
cmp #$C3
bne common_bad
lda mmu_probe_result
ora #BIT_COMMON
sta mmu_probe_result
common_bad:

; ------------------------------------------ page-zero relocation test
; With common disabled, relocate CPU page zero to physical page $20 in
; RAM bank 1. Save/write through $00F0, then later select bank 1 and
; inspect physical $20F0 directly.
lda probe_rcr_none
sta MMU_RCR
lda #$01 ; P0 target is in RAM bank 1
sta MMU_P0H ; high first, low commits translation
lda #$20
sta MMU_P0L
lda $00F0
sta saved_p0_target
lda #$77
sta $00F0

; Prepare a bank-1 page-$21 sentinel at the exact hardware-stack
; offset. P1 common-priority testing must leave this byte unchanged.
lda #$01
sta MMU_P0H
lda #$21
sta MMU_P0L
ldx saved_sp
lda $0000,x
sta saved_p1_target
lda #$33
sta $0000,x

jsr restore_p0
lda probe_rcr_common
sta MMU_RCR
lda #CR_BANK1_RAM_KERNAL_IO
sta MMU_CR
lda $20F0
sta mmu_probe_observed_p0
cmp #$77
bne p0_reloc_bad
lda mmu_probe_result
ora #BIT_P0_RELOC
sta mmu_probe_result
p0_reloc_bad:
lda #CR_BANK0_RAM_KERNAL_IO
sta MMU_CR

; -------------------------------------- page-zero common-RAM priority
; Observe where a write lands when P0 aims at bank 1 page $21 while
; bottom common RAM is active: logical $00F1, bank0:$21F1, or
; bank1:$21F1. Originals of all three bytes are saved and restored.
lda #CR_BANK1_RAM_KERNAL_IO
sta MMU_CR
lda $21F1
sta saved_b1_21f1
lda #CR_BANK0_RAM_KERNAL_IO
sta MMU_CR
lda $21F1
sta saved_b0_21f1
lda $00F1
sta saved_zp_f1
lda #$01
sta MMU_P0H
lda #$21
sta MMU_P0L
lda #$66
sta $00F1
jsr restore_p0
lda $00F1
sta mmu_probe_obs_p0c_zp
lda $21F1
sta mmu_probe_obs_p0c_b0
lda saved_b0_21f1
sta $21F1
lda #CR_BANK1_RAM_KERNAL_IO
sta MMU_CR
lda $21F1
sta mmu_probe_obs_p0c_b1
lda saved_b1_21f1
sta $21F1
lda #CR_BANK0_RAM_KERNAL_IO
sta MMU_CR
; Observed VICE 3.10 model: the page translation applies, but because
; logical page 0 lies in common RAM the pointer's bank bit is forced
; to bank 0. Only bank0:$21F1 receives the write.
lda mmu_probe_obs_p0c_zp
cmp #$66
beq p0_common_bad
lda mmu_probe_obs_p0c_b0
cmp #$66
bne p0_common_bad
lda mmu_probe_obs_p0c_b1
cmp #$66
beq p0_common_bad
lda mmu_probe_result
ora #BIT_P0_COMMON
sta mmu_probe_result
p0_common_bad:
lda saved_zp_f1
sta $00F1

; --------------------------------------- page-one common-RAM priority
; With bottom common active, P1 aimed at bank 1 page $21 follows the
; same model as P0: the stack moves to bank0:$21xx, not bank 1. PHA/PLA
; stays coherent, the bank-1 sentinel stays $33, bank0:$01xx is not
; written, and bank0:$2100+SP receives the pushed byte.
ldx saved_sp
lda $2100,x
sta saved_b0_21sp
lda #$01
sta MMU_P1H
lda #$21
sta MMU_P1L
lda #$5A
pha
pla
cmp #$5A
bne p1_stack_bad
inc p1_stack_ok
p1_stack_bad:
; Restore P1 inline. A JSR here would push its return address through
; the very mapping under test and then change that mapping before RTS.
lda saved_p1h
sta MMU_P1H
lda saved_p1l
sta MMU_P1L
ldx saved_sp ; PHA wrote at offset saved_sp
lda $0100,x
sta mmu_probe_obs_p1c_stack
cmp #$5A
bne p1_page1_clean
dec p1_stack_ok ; stack was not relocated at all
p1_page1_clean:
lda $2100,x
sta mmu_probe_obs_p1c_b0
cmp #$5A
beq p1_b0_hit
dec p1_stack_ok ; push did not land in bank0 page $21
p1_b0_hit:
lda saved_b0_21sp
sta $2100,x

; Disable common RAM, inspect/restore the bank-1 sentinel through a
; relocated page zero, and restore the earlier page-zero target too.
lda probe_rcr_none
sta MMU_RCR
lda #$01
sta MMU_P0H
lda #$21
sta MMU_P0L
ldx saved_sp
lda $0000,x
sta mmu_probe_observed_p1
cmp #$33
bne p1_sentinel_bad
lda p1_stack_ok
cmp #$01
bne p1_sentinel_bad
lda mmu_probe_result
ora #BIT_P1_COMMON
sta mmu_probe_result
p1_sentinel_bad:
lda saved_p1_target
sta $0000,x

lda #$01
sta MMU_P0H
lda #$20
sta MMU_P0L
lda saved_p0_target
sta $00F0
jsr restore_p0

; --------------------------------------------------------- cleanup
lda probe_rcr_common
sta MMU_RCR
lda #CR_BANK1_RAM_KERNAL_IO
sta MMU_CR
lda saved_bank1_byte
sta $4000
lda #CR_BANK0_RAM_KERNAL_IO
sta MMU_CR
lda saved_bank0_byte
sta $4000

jsr restore_p0
jsr restore_p1
lda saved_rcr
sta MMU_RCR
lda saved_cr
sta MMU_CR

; All visible MMU state must match the entry state before interrupts
; are restored or any KERNAL routine is called.
lda MMU_CR
cmp saved_cr
bne restore_bad
lda MMU_RCR
cmp saved_rcr
bne restore_bad
lda MMU_P0L
cmp saved_p0l
bne restore_bad
lda MMU_P0H
cmp saved_p0h
bne restore_bad
lda MMU_P1L
cmp saved_p1l
bne restore_bad
lda MMU_P1H
cmp saved_p1h
bne restore_bad
lda mmu_probe_result
ora #BIT_RESTORED
sta mmu_probe_result
restore_bad:

lda #$A5
sta mmu_probe_complete
mmu_probe_done:
plp ; restore BASIC's original IRQ state

ldx #$00
lda mmu_probe_result
cmp #$FF
beq print_ok_loop
print_fail_loop:
lda fail_message,x
beq print_done
txa
pha
lda fail_message,x
jsr CHROUT
pla
tax
inx
bne print_fail_loop
print_ok_loop:
lda ok_message,x
beq print_done
txa
pha
lda ok_message,x
jsr CHROUT
pla
tax
inx
bne print_ok_loop
print_done:
rts

; Runs with bank 1 selected. Code, data, zero page, and stack are all visible
; because the bottom 8K is common bank-0 RAM.
bank1_return_probe:
lda #$5A
sta return_marker
rts

restore_p0:
lda saved_p0h ; high first, low commits pointer update
sta MMU_P0H
lda saved_p0l
sta MMU_P0L
rts

restore_p1:
lda saved_p1h
sta MMU_P1H
lda saved_p1l
sta MMU_P1L
rts
mmu_probe_code_end: ; verifier: MMU stores must come from below

.segment "RODATA"
ok_message:
.byte $0D, "NOVA128 MMU PROBE OK", $0D, $00
fail_message:
.byte $0D, "NOVA128 MMU PROBE FAIL", $0D, $00

.segment "DATA"
mmu_probe_result:
.byte $00
mmu_probe_complete:
.byte $00
mmu_probe_observed_bank0:
.byte $00
mmu_probe_observed_bank1:
.byte $00
mmu_probe_observed_p0:
.byte $00
mmu_probe_observed_p1:
.byte $00
mmu_probe_obs_p0c_zp: ; logical $00F1 after P0 restored
.byte $00
mmu_probe_obs_p0c_b0: ; bank0:$21F1
.byte $00
mmu_probe_obs_p0c_b1: ; bank1:$21F1
.byte $00
mmu_probe_obs_p1c_stack: ; bank0:$0100+SP after relocated PHA
.byte $00
mmu_probe_obs_p1c_b0: ; bank0:$2100+SP after relocated PHA
.byte $00
saved_b0_21sp: .byte $00
saved_b0_21f1: .byte $00
saved_b1_21f1: .byte $00

mmu_probe_saved_cr:
saved_cr: .byte $00
mmu_probe_saved_rcr:
saved_rcr: .byte $00
mmu_probe_saved_p0l:
saved_p0l: .byte $00
mmu_probe_saved_p0h:
saved_p0h: .byte $00
mmu_probe_saved_p1l:
saved_p1l: .byte $00
mmu_probe_saved_p1h:
saved_p1h: .byte $00
saved_sp: .byte $00
mmu_probe_common_rcr:
probe_rcr_common: .byte $00
probe_rcr_none: .byte $00
saved_bank0_byte: .byte $00
saved_bank1_byte: .byte $00
saved_p0_target: .byte $00
saved_p1_target: .byte $00
saved_zp_f1: .byte $00
common_marker: .byte $00
common_stage: .byte $00
return_marker: .byte $00
p1_stack_ok: .byte $00


+ 122
- 0
newLang/tests/disk/prepare-disk.tests.ps1 Vedi File

@@ -0,0 +1,122 @@
# E07-S01 integration checks for scripts/prepare-disk.ps1.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"

$project = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$repository = Split-Path $project -Parent
$prepare = Join-Path $project "scripts\prepare-disk.ps1"
$buildMinimal = Join-Path $project "scripts\build-minimal.ps1"
$source = Join-Path $repository "vim\work.d81"
$c1541 = "C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe"
$testRoot = Join-Path $project "build\disk-test"
$output = Join-Path $testRoot "newlang-test.d81"
$examples = Join-Path $testRoot "examples"
$testCompiler = Join-Path $testRoot "n128c-test.prg"
$missing = Join-Path $testRoot "missing"

if (-not (Test-Path -LiteralPath $prepare -PathType Leaf)) {
throw "prepare-disk.ps1 not found"
}
if (-not (Test-Path -LiteralPath $source -PathType Leaf)) {
throw "test source D81 not found: $source"
}
if (-not (Test-Path -LiteralPath $c1541 -PathType Leaf)) {
throw "test c1541 not found: $c1541"
}

New-Item -ItemType Directory -Force -Path $examples | Out-Null
[IO.File]::WriteAllText((Join-Path $examples "demo.n8"),
"program Main`n print `"Nova128`"`nend program`n",
[Text.UTF8Encoding]::new($false))

# E01-S01 is this story's completed dependency and provides a known-good PRG.
& $buildMinimal | Out-Host
$minimal = Join-Path $project "build\minimal.prg"
Copy-Item -LiteralPath $minimal -Destination $testCompiler -Force

$sourceHashBefore = (Get-FileHash -LiteralPath $source -Algorithm SHA256).Hash

$first = & $prepare -SourceImage $source -OutputImage $output -C1541Path $c1541 `
-CompilerPrg $testCompiler -ProbePrg $minimal -ExamplesDirectory $examples `
-RequireCompiler
if (-not (Test-Path -LiteralPath $output -PathType Leaf)) {
throw "prepare-disk did not create the disposable image"
}
$firstHash = (Get-FileHash -LiteralPath $output -Algorithm SHA256).Hash
$listing = (& $c1541 $output -list 2>&1) -join "`n"
foreach ($expected in @(
'(?im)"n128c"\s+prg\s*$',
'(?im)"n128probe"\s+prg\s*$',
'(?im)"demo\.n8"\s+seq\s*$')) {
if ($listing -notmatch $expected) {
throw "prepared directory lacks expected entry $expected`n$listing"
}
}

# Damage only the generated output, then prove a rebuild returns to the exact
# same bytes from the clean source image.
[IO.File]::WriteAllBytes($output, [byte[]](1, 2, 3, 4))
$second = & $prepare -SourceImage $source -OutputImage $output -C1541Path $c1541 `
-CompilerPrg $testCompiler -ProbePrg $minimal -ExamplesDirectory $examples `
-RequireCompiler
$secondHash = (Get-FileHash -LiteralPath $output -Algorithm SHA256).Hash
if ($firstHash -ne $secondHash) {
throw "two clean prepares produced different D81 hashes"
}

$sourceHashAfter = (Get-FileHash -LiteralPath $source -Algorithm SHA256).Hash
if ($sourceHashAfter -ne $sourceHashBefore) {
throw "source D81 hash changed during prepare test"
}

# Optional, not-yet-built artifacts must be skipped without preventing a clean
# copy. This deliberately uses absent compiler, probe, and examples paths.
$optionalOutput = Join-Path $testRoot "optional-only.d81"
$optional = & $prepare -SourceImage $source -OutputImage $optionalOutput `
-C1541Path $c1541 -CompilerPrg (Join-Path $missing "n128c.prg") `
-ProbePrg (Join-Path $missing "probe.prg") `
-ExamplesDirectory (Join-Path $missing "examples") 3>&1
if (-not (Test-Path -LiteralPath $optionalOutput -PathType Leaf)) {
throw "missing optional artifacts prevented creation of the copied D81"
}

function Assert-FailsWith {
param(
[Parameter(Mandatory = $true)][scriptblock]$Action,
[Parameter(Mandatory = $true)][string]$Pattern
)
$caught = $null
try {
& $Action
} catch {
$caught = $_.Exception.Message
}
if ($null -eq $caught) {
throw "expected failure matching '$Pattern', but the command succeeded"
}
if ($caught -notmatch $Pattern) {
throw "failure '$caught' did not match '$Pattern'"
}
}

Assert-FailsWith -Pattern 'source D81 not found' -Action {
& $prepare -SourceImage (Join-Path $missing "work.d81") `
-OutputImage (Join-Path $testRoot "never-source.d81") `
-C1541Path $c1541
}
Assert-FailsWith -Pattern 'c1541 tool not found' -Action {
& $prepare -SourceImage $source `
-OutputImage (Join-Path $testRoot "never-tool.d81") `
-C1541Path (Join-Path $missing "c1541.exe")
}
Assert-FailsWith -Pattern 'must not be the source' -Action {
& $prepare -SourceImage $source -OutputImage $source -C1541Path $c1541
}

Write-Host "prepare-disk tests passed"
Write-Host "source SHA-256 unchanged: $sourceHashAfter"
Write-Host "deterministic output SHA-256: $secondHash"
Write-Host "directory entries verified: n128c, n128probe, demo.n8"


+ 79
- 0
newLang/tests/grammar/README.md Vedi File

@@ -0,0 +1,79 @@
# Grammar Fixtures

These fixtures define the accepted and rejected surface grammar before parser
implementation. They are readable ASCII mirrors of Nova128 source. Later
PETSCII conversion tests must derive from these sources rather than replacing
them as the authoritative text.

Run the structural check from `newLang/`:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/grammar/validate.ps1
```

The validator enforces the Vim editor profile for every `.n8` fixture:

- base name at most 12 characters;
- at most 80 ASCII bytes per logical line;
- at most 18,999 source bytes;
- at most 599 logical lines;
- one symbolic diagnostic expectation for every rejected fixture;
- no diagnostic sidecars for accepted fixtures.

`.expect` files deliberately use symbolic diagnostic names. Story E02-S04 will
assign stable numeric codes without requiring grammar fixtures to guess them.

## Accepted Coverage

| Production or form | Fixture evidence |
|---|---|
| Empty file body and program declaration | `accepted/minimal.n8` |
| Comment and EOL | `accepted/minimal.n8` |
| Interface declaration and method signatures | `decls.n8`, `forward.n8` |
| Class declaration and `implements` list | `decls.n8` |
| Forward top-level type reference | `forward.n8` |
| Field declaration, access, and fixed array type | `decls.n8` |
| Default field/method access | `nulls.n8` |
| Method declaration, parameters, locals, result type | `decls.n8`, `stmts.n8` |
| `init` and `destroy` declarations | `decls.n8` |
| Program variable declarations | all non-minimal accepted fixtures |
| Assignment to local, field, and array element | `stmts.n8`, `arrays.n8` |
| Call statement and call expression | `stmts.n8` |
| `if`, `else`, and `end if` | `decls.n8`, `stmts.n8`, `exprs.n8` |
| `if` without `else` | `exprs.n8` |
| `while` | `stmts.n8` |
| `for` with and without `step` | `stmts.n8` |
| `return` with and without a value | `decls.n8`, `nulls.n8` |
| `print` literal and scalar | `stmts.n8`, `exprs.n8` |
| `delete` concrete and interface references | `stmts.n8`, `decls.n8` |
| `new` expression and constructor arguments | `decls.n8`, `stmts.n8` |
| Member access, method call, and array indexing | `decls.n8`, `arrays.n8` |
| Parentheses and all documented operator levels | `exprs.n8` |
| Decimal number, string escape, character literal, booleans, and `null` | `exprs.n8`, `decls.n8`, `nulls.n8` |

Hexadecimal and binary numeric forms are lexer behavior and belong to E02-S02
token fixtures, not parser grammar coverage.

## Rejected Coverage

| Required rejection | Fixture | Expected symbolic diagnostic |
|---|---|---|
| Missing program terminator | `miss_end.n8` | `SYN_EXPECTED_END_PROGRAM` |
| Invalid expression continuation | `line_cont.n8` | `SYN_UNEXPECTED_EOL` |
| Duplicate declaration | `dup_decl.n8` | `SEM_DUPLICATE_NAME` |
| Malformed interface header | `bad_iface.n8` | `SYN_EXPECTED_IDENTIFIER` |
| Malformed class header | `bad_class.n8` | `SYN_EXPECTED_INTERFACE_NAME` |
| Missing method terminator | `miss_meth.n8` | `SYN_EXPECTED_END_METHOD` |
| Missing `if` terminator | `miss_if.n8` | `SYN_EXPECTED_END_IF` |
| Missing class terminator | `miss_class.n8` | `SYN_EXPECTED_END_CLASS` |
| Multiple statements on one line | `multi_stmt.n8` | `SYN_EXPECTED_EOL` |
| Interface method body | `iface_body.n8` | `SYN_INTERFACE_METHOD_BODY` |
| Malformed method parameter list | `bad_method.n8` | `SYN_EXPECTED_RPAREN` |
| Character literal with multiple characters | `bad_char.n8` | `LEX_BAD_CHAR_LITERAL` |

## Future Parser Contract

Accepted fixtures must parse and pass declaration-level semantic checks.
Rejected fixtures assert the first primary diagnostic named in their sidecar;
later recovery diagnostics do not invalidate that expectation.


+ 23
- 0
newLang/tests/grammar/accepted/arrays.n8 Vedi File

@@ -0,0 +1,23 @@
class Buffer
private byte[4] data

public method set(index as word, value as byte)
self.data[index] = value
end method

public method get(index as word) as byte
return self.data[index]
end method
end class

program Main
var buffer as Buffer
var values as word[4]
var item as byte
buffer = new Buffer()
values[0] = 1
buffer.set(values[0], 7)
item = buffer.get(values[0])
print item
delete buffer
end program

+ 50
- 0
newLang/tests/grammar/accepted/decls.n8 Vedi File

@@ -0,0 +1,50 @@
interface Printable
method print()
end interface

interface Resettable
method reset()
method active() as boolean
end interface

class Counter implements Printable, Resettable
private word value
private byte[4] history

public method init(start as word)
var copy as word
copy = start
self.value = copy
self.history[0] = 0
end method

public method destroy()
self.value = 0
return
end method

public method print()
print self.value
end method

public method reset()
self.value = 0
end method

public method active() as boolean
if self.value <> 0
return true
else
return false
end if
end method
end class

program Main
var count as Counter
var output as Printable
count = new Counter(10)
output = count
output.print()
delete output
end program

+ 34
- 0
newLang/tests/grammar/accepted/exprs.n8 Vedi File

@@ -0,0 +1,34 @@
class Formula
public method eval(a as integer, b as integer) as integer
var result as integer
result = -(a + b) * 2 / 1
if not (a = b) and (a <> 0 or b >= a)
result = result + a - b
else
result = 0
end if
if a < b
result = result + 1
end if
if a <= b
result = result + 1
end if
if a > b
result = result - 1
end if
return result
end method
end class

program Main
var formula as Formula
var answer as integer
var glyph as char
formula = new Formula()
answer = formula.eval(-2, 3)
glyph = 'A'
print "answer\n"
print answer
print glyph
delete formula
end program

+ 24
- 0
newLang/tests/grammar/accepted/forward.n8 Vedi File

@@ -0,0 +1,24 @@
class Runner implements Job
public method execute(code as byte) as boolean
if code = 0
return false
else
return true
end if
end method
end class

interface Job
method execute(code as byte) as boolean
end interface

program Main
var runner as Runner
var job as Job
var ok as boolean
runner = new Runner()
job = runner
ok = job.execute(1)
print ok
delete job
end program

+ 3
- 0
newLang/tests/grammar/accepted/minimal.n8 Vedi File

@@ -0,0 +1,3 @@
// The smallest valid Nova128 compilation unit.
program Main
end program

+ 19
- 0
newLang/tests/grammar/accepted/nulls.n8 Vedi File

@@ -0,0 +1,19 @@
interface Marker
method mark()
end interface

class Empty implements Marker
word value

method mark()
return
end method
end class

program Main
var object as Empty
var marker as Marker
object = null
marker = null
delete marker
end program

+ 44
- 0
newLang/tests/grammar/accepted/stmts.n8 Vedi File

@@ -0,0 +1,44 @@
class Worker
private word value

public method init(start as word)
self.value = start
end method

public method tick(amount as word)
self.value = self.value + amount
end method

public method run(limit as word) as word
var i as word
var total as word
i = 0
total = 0
while i < limit
total = total + i
i = i + 1
end while
if total > 100
print "large"
else
print total
end if
for i = 0 to 3 step 1
self.tick(i)
end for
for i = 0 to 1
self.tick(i)
end for
return total
end method
end class

program Main
var worker as Worker
var total as word
worker = new Worker(1)
worker.tick(1)
total = worker.run(4)
print total
delete worker
end program

+ 4
- 0
newLang/tests/grammar/rejected/bad_char.expect Vedi File

@@ -0,0 +1,4 @@
diagnostic: LEX_BAD_CHAR_LITERAL
line: 3
column: 11
note: a character literal contains exactly one character or supported escape

+ 4
- 0
newLang/tests/grammar/rejected/bad_char.n8 Vedi File

@@ -0,0 +1,4 @@
program Main
var glyph as char
glyph = 'AB'
end program

+ 3
- 0
newLang/tests/grammar/rejected/bad_class.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_INTERFACE_NAME
line: 1
reason: implements requires at least one interface name

+ 5
- 0
newLang/tests/grammar/rejected/bad_class.n8 Vedi File

@@ -0,0 +1,5 @@
class Widget implements
end class

program Main
end program

+ 3
- 0
newLang/tests/grammar/rejected/bad_iface.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_IDENTIFIER
line: 1
reason: an interface declaration requires a name on its header line

+ 6
- 0
newLang/tests/grammar/rejected/bad_iface.n8 Vedi File

@@ -0,0 +1,6 @@
interface
method print()
end interface

program Main
end program

+ 3
- 0
newLang/tests/grammar/rejected/bad_method.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_RPAREN
line: 2
reason: a method parameter list must end with a right parenthesis

+ 8
- 0
newLang/tests/grammar/rejected/bad_method.n8 Vedi File

@@ -0,0 +1,8 @@
class Counter
public method set(value as word
self.value = value
end method
end class

program Main
end program

+ 3
- 0
newLang/tests/grammar/rejected/dup_decl.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SEM_DUPLICATE_NAME
line: 3
reason: value is declared twice in the program-local namespace

+ 4
- 0
newLang/tests/grammar/rejected/dup_decl.n8 Vedi File

@@ -0,0 +1,4 @@
program Main
var value as word
var value as byte
end program

+ 3
- 0
newLang/tests/grammar/rejected/iface_body.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_INTERFACE_METHOD_BODY
line: 3
reason: interface methods are signatures and cannot contain a body

+ 8
- 0
newLang/tests/grammar/rejected/iface_body.n8 Vedi File

@@ -0,0 +1,8 @@
interface Printable
method print()
print "body"
end method
end interface

program Main
end program

+ 3
- 0
newLang/tests/grammar/rejected/line_cont.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_UNEXPECTED_EOL
line: 3
reason: version 1 expressions cannot continue across a logical line

+ 5
- 0
newLang/tests/grammar/rejected/line_cont.n8 Vedi File

@@ -0,0 +1,5 @@
program Main
var total as word
total = 1 +
2
end program

+ 3
- 0
newLang/tests/grammar/rejected/miss_class.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_END_CLASS
line: 4
reason: a program declaration cannot begin inside an open class

+ 5
- 0
newLang/tests/grammar/rejected/miss_class.n8 Vedi File

@@ -0,0 +1,5 @@
class Counter
private word value

program Main
end program

+ 3
- 0
newLang/tests/grammar/rejected/miss_end.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_END_PROGRAM
line: 3
reason: end-of-file reached before the required end program terminator

+ 2
- 0
newLang/tests/grammar/rejected/miss_end.n8 Vedi File

@@ -0,0 +1,2 @@
program Main
print "unfinished"

+ 3
- 0
newLang/tests/grammar/rejected/miss_if.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_END_IF
line: 4
reason: end program cannot terminate an open if statement

+ 4
- 0
newLang/tests/grammar/rejected/miss_if.n8 Vedi File

@@ -0,0 +1,4 @@
program Main
if true
print "yes"
end program

+ 3
- 0
newLang/tests/grammar/rejected/miss_meth.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_END_METHOD
line: 4
reason: end class cannot terminate an open method declaration

+ 7
- 0
newLang/tests/grammar/rejected/miss_meth.n8 Vedi File

@@ -0,0 +1,7 @@
class Counter
public method get() as word
return 0
end class

program Main
end program

+ 3
- 0
newLang/tests/grammar/rejected/multi_stmt.expect Vedi File

@@ -0,0 +1,3 @@
diagnostic: SYN_EXPECTED_EOL
line: 2
reason: version 1 permits only one statement on a logical line

+ 3
- 0
newLang/tests/grammar/rejected/multi_stmt.n8 Vedi File

@@ -0,0 +1,3 @@
program Main
print 1 print 2
end program

+ 72
- 0
newLang/tests/grammar/validate.ps1 Vedi File

@@ -0,0 +1,72 @@
$ErrorActionPreference = "Stop"

$root = $PSScriptRoot
$accepted = Join-Path $root "accepted"
$rejected = Join-Path $root "rejected"
$errors = New-Object System.Collections.Generic.List[string]

function Test-SourceProfile([System.IO.FileInfo]$file) {
if ($file.BaseName.Length -gt 12) {
$errors.Add("$($file.FullName): base name exceeds 12 characters")
}

$bytes = [IO.File]::ReadAllBytes($file.FullName)
if ($bytes.Length -gt 18999) {
$errors.Add("$($file.FullName): $($bytes.Length) bytes exceeds 18999")
}

$text = [Text.Encoding]::ASCII.GetString($bytes)
$lines = $text -split "`r`n|`n|`r"
if ($lines.Count -gt 599) {
$errors.Add("$($file.FullName): $($lines.Count) lines exceeds 599")
}
for ($i = 0; $i -lt $lines.Count; $i++) {
$count = [Text.Encoding]::ASCII.GetByteCount($lines[$i])
if ($count -gt 80) {
$errors.Add("$($file.FullName):$($i + 1): $count bytes exceeds 80")
}
}
}

$acceptedSources = @(Get-ChildItem -LiteralPath $accepted -Filter "*.n8" -File)
$rejectedSources = @(Get-ChildItem -LiteralPath $rejected -Filter "*.n8" -File)

if ($acceptedSources.Count -eq 0) { $errors.Add("no accepted fixtures found") }
if ($rejectedSources.Count -eq 0) { $errors.Add("no rejected fixtures found") }

foreach ($file in $acceptedSources) {
Test-SourceProfile $file
$sidecar = [IO.Path]::ChangeExtension($file.FullName, ".expect")
if (Test-Path -LiteralPath $sidecar) {
$errors.Add("${sidecar}: accepted fixtures must not have diagnostic sidecars")
}
}

foreach ($file in $rejectedSources) {
Test-SourceProfile $file
$sidecar = [IO.Path]::ChangeExtension($file.FullName, ".expect")
if (-not (Test-Path -LiteralPath $sidecar)) {
$errors.Add("$($file.FullName): missing .expect sidecar")
continue
}
$expect = Get-Content -Raw -LiteralPath $sidecar
$matches = [regex]::Matches($expect, '(?m)^diagnostic: ([A-Z][A-Z0-9_]+)$')
if ($matches.Count -ne 1) {
$errors.Add("${sidecar}: expected exactly one symbolic diagnostic")
}
}

$orphanSidecars = @(Get-ChildItem -LiteralPath $rejected -Filter "*.expect" -File |
Where-Object { -not (Test-Path -LiteralPath ([IO.Path]::ChangeExtension($_.FullName, ".n8"))) })
foreach ($orphan in $orphanSidecars) {
$errors.Add("$($orphan.FullName): orphan diagnostic sidecar")
}

if ($errors.Count -gt 0) {
$errors | ForEach-Object { Write-Error $_ }
exit 1
}

Write-Host ("grammar fixtures ok: {0} accepted, {1} rejected" -f
$acceptedSources.Count, $rejectedSources.Count)


Loading…
Annulla
Salva

Powered by TurnKey Linux.