Session 1: hardware research, content measurement, codec decision, MAME harness
Verified GVRAM is one word-access per pixel in ALL color modes; chose 256-color 256x192 with movem.l bursts (page 1 sacrificed as double-buffer). Measured 8 scenes from the Blu-ray source: blit costs under 8% of the 12fps cycle budget, so I/O is the bottleneck, not CPU. Naive delta+RLE reaches only 3.2:1 (365 KB/s, 470MB) -> decision to use 4x4 vector quantization (~30 KB/s). "Shot on twos" assumption failed: the transfer has zero duplicate frames, so 12fps requires explicit decimation. Documents three false measurement results and their root causes (per-frame Floyd-Steinberg dithering, temporal denoise, exact-match dedupe on noisy source). MAME Lua injection harness works and is reusable for cycle-cost measurement; the IOCS _B_READ disk benchmark is blocked returning -1. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
*.hds
|
||||
*.iso
|
||||
*.m2ts
|
||||
*.o
|
||||
*.x
|
||||
assets/
|
||||
assets/audio/
|
||||
assets/frames/
|
||||
build/
|
||||
roms/
|
||||
@@ -0,0 +1,27 @@
|
||||
# Dragon's Lair — Sharp X68000 port
|
||||
|
||||
Porting Dragon's Lair to a stock X68000 (68000 @ 10MHz, 2MB, SASI/SCSI).
|
||||
|
||||
This is fundamentally a **video codec problem**, not a game-logic problem: the
|
||||
game logic is a scene table with branching input windows; the difficulty is
|
||||
pushing ~22 minutes of Don Bluth animation through a 10MHz 68000.
|
||||
|
||||
## Read first
|
||||
- **`docs/FINDINGS.md`** — measured hardware facts, content statistics, codec
|
||||
decision, and a section on measurement traps that produced three separate
|
||||
false results. Read §4 before trusting any pipeline number.
|
||||
- **`docs/STATUS.md`** — current state, working setup, blockers, next steps.
|
||||
- **`docs/HARDWARE.md`** — X68000 GVRAM/CRTC reference.
|
||||
|
||||
## Layout
|
||||
```
|
||||
docs/ findings, status, hardware reference
|
||||
tools/analysis/ frame-analysis scripts (01/02 marked BROKEN as regression refs)
|
||||
tools/bench/ MAME Lua injection harness + 68000 benchmark sources
|
||||
tools/vasm/ vasm m68k assembler (built from source)
|
||||
tools/encoder/ VQ encoder (not yet written)
|
||||
src/player/ 68000 player (not yet written)
|
||||
assets/ extracted frames/audio (gitignored)
|
||||
```
|
||||
|
||||
Source media (`DRAGONS_LAIR.iso`) and ROMs are gitignored — supply your own.
|
||||
@@ -0,0 +1,190 @@
|
||||
# Findings — session 1 (2026-08-23)
|
||||
|
||||
All numbers here are MEASURED unless marked ESTIMATE or FOLKLORE.
|
||||
|
||||
---
|
||||
|
||||
## 1. Source material
|
||||
|
||||
`DRAGONS_LAIR.iso` — 16 GB, UDF 2.x, **decrypted** (no AACS dir).
|
||||
Loop-mounted read-only at `/media/reala-misaki/BDROM` via `udisksctl loop-setup -r -f`.
|
||||
(7-Zip cannot read UDF 2.x; use the loop mount.)
|
||||
|
||||
- **224 `.m2ts` streams**, 1920x1080, **MPEG-2, progressive, 23.976 fps**
|
||||
- Size histogram: 47 <5MB, 138 5-50MB, 22 50-150MB, 14 150-400MB, 3 >400MB
|
||||
- The 185 sub-50MB streams are the **arcade branching scenes already split into
|
||||
individual clips** — we get scene boundaries for free.
|
||||
- Big streams are full-feature playthroughs: 00215 (1376s), 00216 (1151s), 00223 (566s)
|
||||
- Typical scene clip ~60s (00203/00205/00199), some ~100s (00164/00212)
|
||||
|
||||
**Gotcha:** clip durations vary wildly. Always read `format=duration` and seek
|
||||
relative to it. Seeking to a fixed offset silently yields 0 frames on short clips.
|
||||
|
||||
---
|
||||
|
||||
## 2. GVRAM layout [verified — see HARDWARE.md for source]
|
||||
|
||||
**One 16-bit word per pixel position in EVERY color mode.** Bit depth does not
|
||||
change VRAM bandwidth; it only subdivides the word.
|
||||
|
||||
`addr = page_base + y*1024 + x*2` — adjacent pixels are 2 bytes apart in all modes.
|
||||
|
||||
Consequence: low bit depth buys **no speed**. 16-color mode is strictly worse than
|
||||
256-color (same bus traffic, 1/16 the palette). Page-alias writes are hardware
|
||||
auto-masked, so 16-color needs no software read-modify-write — but it's still
|
||||
one word-access per pixel.
|
||||
|
||||
**Chosen: 256 colors, 256x192 active area.**
|
||||
In 256-color mode P0=low byte, P1=high byte of each word. Sacrificing page 1 as a
|
||||
double-buffer lets a `move.l` cover two pixel positions, enabling `movem.l` bursts
|
||||
(12 regs = 48 bytes = 24 pixels). Identical blit cost to 65536-color mode but
|
||||
**half the on-disk data**.
|
||||
|
||||
---
|
||||
|
||||
## 3. Content measurements (8 scenes sampled, 5s each at 40% into each clip)
|
||||
|
||||
| metric | mean | p90 |
|
||||
|---|---|---|
|
||||
| pixels changed / frame | 20.1% | 30.2% |
|
||||
| **blit cost** | **~64k cycles** | **~97k cycles** |
|
||||
| naive delta+RLE frame size | 15.5 KB | 19.6 KB |
|
||||
|
||||
Budget is **833,333 cycles/frame** @ 12fps on a 10MHz 68000.
|
||||
|
||||
### => THE CPU IS NOT THE BOTTLENECK. I/O IS.
|
||||
Blit uses **under 8%** of budget. The naive row-span+RLE codec achieves only
|
||||
**3.2:1**, giving **365 KB/s / 470 MB** at 24fps (~183 KB/s / 235 MB at 12fps).
|
||||
|
||||
Per-scene variance is extreme: static dialogue ~30 KB/s, action ~700 KB/s.
|
||||
Any codec needs a hard bitrate ceiling, not just a good average.
|
||||
|
||||
### "Shot on twos" — ASSUMPTION FAILED
|
||||
Dedupe found **zero** duplicate frames across all 8 scenes (`uniq=120/120`,
|
||||
24.0 fps effective). This Blu-ray is a restoration where every frame is unique.
|
||||
We do NOT get halved data for free. **Decimation to 12fps must be explicit.**
|
||||
|
||||
A weak alternation signature does exist (even-index pairs 40.7% vs odd 27.5%,
|
||||
ratio 1.5x, with occasional true-duplicate pairs at 0.03-0.19%), but it is
|
||||
irregular — Bluth mixed ones and twos; action is animated on ones.
|
||||
|
||||
---
|
||||
|
||||
## 4. MEASUREMENT TRAPS — read before trusting any pipeline number
|
||||
|
||||
Three separate false results were produced and caught this session. All three
|
||||
looked plausible. Guard against them:
|
||||
|
||||
1. **Per-frame Floyd-Steinberg dithering destroys temporal coherence.**
|
||||
Error diffusion is chaotic: a +/-1 input change cascades across the row and
|
||||
produces a completely different index pattern. First run reported 31.5% pixels
|
||||
changed with near-zero variance (median 31.6, p90 32.3, max 32.7) while source
|
||||
mean-abs-diff was 0.09 — i.e. visually identical frames. That flat variance is
|
||||
the tell: **real animation has scene-dependent variance; noise does not.**
|
||||
Use no dithering (cel art is flat) or ordered/Bayer (spatially fixed, temporally stable).
|
||||
|
||||
2. **Temporal denoise smears motion.** `hqdn3d=4:3:6:4` — the `6:4` are temporal
|
||||
params. It flattened real motion, which then measured as "no motion" and
|
||||
produced an absurd 0.8 fps / 4 MB result. **Use spatial-only: `hqdn3d=4:3:0:0`.**
|
||||
|
||||
3. **Exact-match dedupe fails on a noisy source.** MPEG-2 grain means near-duplicate
|
||||
frames differ by +/-1 and are never bit-exact. Use a threshold on
|
||||
"% pixels differing by more than N levels", and pick the threshold from the
|
||||
observed distribution, not a guess. A 2% threshold ate genuine animation when
|
||||
mean consecutive change was only 0.9%.
|
||||
|
||||
**Sanity rule: if a result has suspiciously low variance, or is suspiciously
|
||||
good, it is probably an artifact of the measurement, not a property of the content.**
|
||||
|
||||
Scripts kept in `tools/analysis/` — 01 and 02 are marked BROKEN deliberately as
|
||||
regression references; 03 and 04 are the correct ones.
|
||||
|
||||
---
|
||||
|
||||
## 5. Storage interface — the SASI/SCSI split
|
||||
|
||||
[Yasuma, X68030 internal SCSI controller]
|
||||
|
||||
- Interface: **SCSI-1**, 50-pin, 5 MB/s bus spec
|
||||
- Controller: **Fujitsu MB89352** SPC
|
||||
- Transfer mode: **DMA** (via **HD63450** DMAC)
|
||||
- Bus: X68000 original bus, **16-bit @ 10MHz**
|
||||
|
||||
**Even on the X68030, SCSI runs at 10MHz 16-bit DMA.** Storage bandwidth does
|
||||
NOT scale with CPU — the controller sits on the original bus. HD63450's 12.5MHz
|
||||
official ceiling is why the X68030 runs at 25MHz. An "HSCSI" TSR forces PIO/FIFO
|
||||
transfer instead of DMA but was marginal even at 25MHz.
|
||||
|
||||
Because it's DMA, **streaming costs essentially no CPU** — this stacks with the
|
||||
8% blit utilisation. The 68000 really is nearly idle.
|
||||
|
||||
### Model split — IMPORTANT
|
||||
**The 10MHz models (original X68000, ACE, PRO, EXPERT) use SASI, not SCSI.**
|
||||
Built-in SCSI starts at the X68000 **Super** (1990) and continues through XVI,
|
||||
Compact, X68030. SCSI on earlier machines needs the **Sharp CZ-6BS1** board
|
||||
in an I/O slot (MAME models this: `-exp1 cz6bs1`).
|
||||
|
||||
| target | bandwidth | naive codec (365 KB/s) | VQ codec (~30 KB/s) |
|
||||
|---|---|---|---|
|
||||
| SASI (stock ACE/EXPERT) | ~300-500 KB/s FOLKLORE | infeasible | comfortable |
|
||||
| SCSI (Super+, or CZ-6BS1) | ~1 MB/s FOLKLORE | tight but viable | trivial |
|
||||
|
||||
Derived bounds (ESTIMATE): 16-bit @10MHz with 4-clock bus cycle = 5 MB/s absolute
|
||||
ceiling; HD63450 single-address DMA ~8 clocks/word => ~2.5 MB/s practical ceiling,
|
||||
before SCSI-1 async handshake and drive latency.
|
||||
|
||||
**No measured benchmark was obtained — see STATUS.md.** The ~300-500 KB/s and
|
||||
~1 MB/s figures are folklore-grade; I could not find a primary measurement.
|
||||
|
||||
---
|
||||
|
||||
## 6. Codec decision: vector quantization (Cinepak-style)
|
||||
|
||||
Given ~8x CPU headroom and an I/O ceiling, spend CPU to buy bandwidth.
|
||||
|
||||
- Split frame into 4x4 blocks, encode each as a 1-byte index into a per-scene codebook
|
||||
- Decode = 16-byte copy from a lookup table: nearly free
|
||||
- A **full** frame = 256*192/16 = **3,072 bytes** — a hard 16:1 floor before delta
|
||||
- Add block-level delta on top; action scenes ~2-3 KB/frame
|
||||
- => roughly **30 KB/s, ~40 MB total**, with a *deterministic* bitrate ceiling
|
||||
|
||||
Divergence from the SNES project (below): use a **per-scene codebook with delta
|
||||
updates**, not a per-frame rebuild. We trade adaptivity for bandwidth because we
|
||||
have 2MB RAM to keep a codebook resident and CPU to spare.
|
||||
|
||||
**Risk not yet evaluated:** 4x4 VQ with a 256-entry codebook will visibly soften
|
||||
detail. Bluth's fine ink linework is what suffers. Prototype and eyeball before committing.
|
||||
|
||||
---
|
||||
|
||||
## 7. Comparison: astrobleem/SNES-SuperDragonsLairArcade
|
||||
|
||||
Reached the **same core architecture independently** — "512 tiles per frame" is
|
||||
vector quantization (8x8 codebook + tilemap). Good validation.
|
||||
|
||||
But: the SNES PPU has **no bitmap mode**, so tiles are forced on them by display
|
||||
hardware. The X68000 has a real linear framebuffer, so VQ is a *compression
|
||||
choice* we can tune or drop per-scene.
|
||||
|
||||
**MSU-1 is a bandwidth cheat we don't have.** It's a modern flash-cart coprocessor
|
||||
giving memory-mapped streaming the real SNES never had. Their budget: 512 tiles x
|
||||
32 bytes (4bpp 8x8) + tilemap ~= 18 KB/frame => **~430 KB/s** at 23.976fps.
|
||||
That's *higher* than the 365 KB/s we'd reject on SASI. (ESTIMATE: my arithmetic on
|
||||
their stated tile budget, not a measured figure.)
|
||||
|
||||
Where we're ahead: 256 simultaneous colors from a 65536 palette vs their 4bpp
|
||||
sub-palettes needing a tile-aware palette optimizer plus a spatial smoothing pass
|
||||
to hide 8x8 palette seams. That problem doesn't exist for us. Plus 68000@10MHz
|
||||
vs 65816@3.58MHz, and 2MB vs 128KB.
|
||||
|
||||
**Most valuable thing in that repo is NOT the codec — it's `data/events/`:**
|
||||
516 chapter definitions across 29 scenes as XML, plus
|
||||
`data/chapter_event_inventory.md`. That's the arcade scene graph and input-timing
|
||||
structure, entirely hardware-independent — the whole game-logic layer we'd
|
||||
otherwise reverse-engineer from the arcade ROM.
|
||||
|
||||
**TODO: check their license before planning to reuse it.**
|
||||
Their 516 chapters are finer-grained than our 224 Blu-ray streams, so mapping
|
||||
their event table onto our footage means subdividing streams by timecode.
|
||||
|
||||
Caveat: all of the above is from README/repo-tree summaries, not their source.
|
||||
@@ -0,0 +1,52 @@
|
||||
# X68000 hardware facts (verified, not assumed)
|
||||
|
||||
Target baseline: X68000 ACE/Expert class. 68000 @ 10MHz, 2MB RAM, SCSI HDD.
|
||||
|
||||
## Cycle budget
|
||||
Content is cel animation shot on twos -> 12fps effective.
|
||||
10,000,000 / 12 = **833,333 cycles per frame**. This is the wall.
|
||||
|
||||
## GVRAM layout [verified: JC-000/x68000-dev-guide docs/graphics.md]
|
||||
- 512KB physical, mapped at $C00000-$DFFFFF as aliased page windows.
|
||||
- **One 16-bit word per pixel position, in EVERY color mode.**
|
||||
Bit depth does NOT change VRAM bandwidth. It only subdivides the word.
|
||||
- Address formula (512-wide page): `addr = page_base + y*1024 + x*2`
|
||||
- Horizontally adjacent pixels are 2 bytes apart in all modes.
|
||||
|
||||
| Mode | Pages | Word subdivision | Page aliases |
|
||||
|-------|-------|-----------------------------|-----------------------------------|
|
||||
| 16 | 4 | nibble per page (P3..P0) | $C00000/$C80000/$D00000/$D80000 |
|
||||
| 256 | 2 | byte per page (P0=lo,P1=hi) | $C00000 (P0) / $C80000 (P1) |
|
||||
| 65536 | 1 | whole word | $C00000 |
|
||||
|
||||
Writes via a page alias are **auto-masked and shifted by hardware** into that
|
||||
page's field. No software read-modify-write is needed for 16-color mode.
|
||||
|
||||
## Chosen mode: 256 colors, 256x192 active area
|
||||
Rationale: since every mode is one word-access per pixel, low bit depth buys
|
||||
no speed. 256-color halves on-disk data vs 65536-color for identical blit cost.
|
||||
|
||||
Coalescing trick: in 256-color mode a `move.l` spans two pixel positions across
|
||||
BOTH pages. We sacrifice page 1 as a double-buffer and let it take duplicate
|
||||
data, which lets us use `movem.l` bursts:
|
||||
movem.l d0-d7/a0-a3,(a6) ; 12 regs = 48 bytes = 24 pixels
|
||||
|
||||
Full-frame refresh cost: 49,152 px / 24 = 2,048 bursts
|
||||
~104 cyc/burst + VRAM waits (~1.5x) = ~320k cycles << 833k budget
|
||||
A hard scene cut fits. Delta frames cost far less.
|
||||
|
||||
## Audio
|
||||
MSM6258 ADPCM via HD63450 DMAC. ~7.8KB/s at 15.6kHz mono, near-zero CPU.
|
||||
22 min ~= 10MB.
|
||||
|
||||
## Storage estimate
|
||||
~15,800 frames. Target ~3-5KB/frame compressed -> 50-80MB video + 10MB audio.
|
||||
Stream rate ~40-60KB/s. Well within SCSI sustained throughput.
|
||||
|
||||
---
|
||||
## Corrections applied after session-1 measurement
|
||||
- Storage estimate below was optimistic. Measured naive codec gives ~470MB @24fps.
|
||||
See FINDINGS.md §3. VQ codec targets ~40MB.
|
||||
- "Content is cel animation shot on twos -> 12fps effective" — **the transfer has
|
||||
zero duplicate frames**; 12fps requires explicit decimation. See FINDINGS.md §3.
|
||||
- 10MHz models are **SASI**, not SCSI. See FINDINGS.md §5.
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
# Status & next-session handoff — end of session 1 (2026-08-23)
|
||||
|
||||
## Decisions locked
|
||||
|
||||
| decision | value | why |
|
||||
|---|---|---|
|
||||
| Target CPU | 68000 @ 10MHz (stock) | hardest honest constraint |
|
||||
| Display mode | 256 colors, 256x192 in 256x256 CRTC mode | every mode is 1 word-access/pixel, so 256c is free vs 16c |
|
||||
| Double buffer | **none** — page 1 sacrificed | enables `movem.l` 24px bursts; delta coding needs a RAM reference frame anyway |
|
||||
| Codec | 4x4 vector quantization, per-scene codebook + block delta | CPU is idle, I/O is the ceiling — spend cycles to buy bandwidth |
|
||||
| Framerate | 12 fps, **explicit decimation** | source has zero duplicate frames; no free "twos" win |
|
||||
| Medium | SCSI HDD image (.hds) | but see SASI/SCSI split below |
|
||||
| Emulator | MAME 0.277 x68000 | accurate enough that measured cycles mean something |
|
||||
|
||||
**OPEN QUESTION for the user:** stock 10MHz machines are **SASI**, not SCSI.
|
||||
Three options, not yet chosen:
|
||||
1. Stock 10MHz + SASI (purist) — VQ becomes mandatory
|
||||
2. Stock 10MHz + CZ-6BS1 SCSI board — relieves I/O, keeps CPU honest
|
||||
3. Super/XVI baseline — built-in SCSI, still a 10MHz 68000
|
||||
|
||||
Recommendation: make the codec's bitrate ceiling a **build parameter**, so one
|
||||
encoder serves all three and the target is chosen at package time.
|
||||
|
||||
---
|
||||
|
||||
## Working setup
|
||||
|
||||
**MAME ROMs** — `~/mame/roms/x68000.zip` (present, working).
|
||||
Must pass **`-bios ipl10`**; the default BIOS is `cz600ce`, whose split
|
||||
even/odd IPL halves (`rh-ix0897cezz.ic12` / `rh-ix0898cezz.ic11`) are absent.
|
||||
`-verifyroms` will still report those two as missing — this is expected and harmless.
|
||||
|
||||
Boots headless at ~430-480% speed:
|
||||
```
|
||||
mame x68000 -bios ipl10 -video none -sound none -nothrottle -seconds_to_run 3
|
||||
```
|
||||
|
||||
**Assembler** — vasm built from source, binary at `tools/vasm/vasmm68k_mot`
|
||||
(source tarball alongside it). Verified correct 68000 output.
|
||||
```
|
||||
tools/vasm/vasmm68k_mot -Fbin -o out.bin in.s
|
||||
```
|
||||
|
||||
**Blu-ray** — mount with:
|
||||
```
|
||||
udisksctl loop-setup -r -f DRAGONS_LAIR.iso # -> /media/reala-misaki/BDROM
|
||||
```
|
||||
NOTE: this loop mount is still active from session 1. Re-mount if the machine rebooted.
|
||||
|
||||
---
|
||||
|
||||
## MAME Lua harness — WORKING, reusable
|
||||
|
||||
`tools/bench/*.lua` inject 68000 machine code straight into emulated RAM and time
|
||||
it against the emulated clock. No bootable disk or OS required. This is the
|
||||
measurement rig for all future cycle-cost work (blit timing, decoder benchmarks).
|
||||
|
||||
Pattern:
|
||||
```
|
||||
mame x68000 -bios ipl10 -video none -sound none -nothrottle \
|
||||
-seconds_to_run 30 -plugins -autoboot_script yourscript.lua
|
||||
```
|
||||
|
||||
### Three MAME Lua gotchas — all cost real time, all now solved
|
||||
1. **Retain the notifier subscription.** `emu.add_machine_frame_notifier()` returns
|
||||
a token; if you drop it into a chunk-local it is garbage-collected and the
|
||||
callback **silently stops firing**. Assign it to a **global** (`SUB = ...`).
|
||||
2. **The stack pointer is `SP`, not `A7`** in `cpu.state[...]`.
|
||||
Full list: A0-A6, D0-D7, PC, SP, SR, USP, CURPC, CURFLAGS, IR.
|
||||
3. **`autoboot_script` fires at time=0, before boot** (PC=0). Wait until
|
||||
`machine.time` >= ~5s before injecting, or IOCS is not yet initialised.
|
||||
|
||||
Also: piping MAME through `grep` block-buffers output — write raw to a file when
|
||||
backgrounding, or you will see an empty log and assume a hang.
|
||||
And never `pkill -f 'mame x68000'` — the pattern matches your own shell and kills it
|
||||
(exit 144). Use `pkill -x mame`.
|
||||
|
||||
---
|
||||
|
||||
## BLOCKED: disk throughput benchmark
|
||||
|
||||
**Goal:** measure real SASI/SCSI KB/s to replace the folklore figures in FINDINGS.md §5.
|
||||
|
||||
**Status:** harness fully working; the IOCS call itself fails.
|
||||
|
||||
`IOCS _B_READ` ($46 via `TRAP #15`; d1.hb=PDA, d2.l=position, d3.l=bytes, a1=buffer)
|
||||
returns **`FFFFFFFF` (-1), zero reads**, uniformly across:
|
||||
- all 16 PDA values $80-$8F
|
||||
- both d1 encodings (PDA in bits 31-24 and bits 15-8)
|
||||
- image sizes 10MB / 20MB / 40MB
|
||||
|
||||
The uniformity is the diagnostic: calls are **dispatched and cleanly rejected**,
|
||||
so `TRAP #15` and IOCS are reachable. MAME does mount the image
|
||||
(`:x68k_hdc: opened image file bench.hdf`).
|
||||
|
||||
**Untested hypotheses, in rough order of likelihood:**
|
||||
1. The raw image has no X68000 SASI format, so the IPL's boot scan never registered
|
||||
a usable drive and IOCS refuses. Would need Human68k to format one — **we have
|
||||
no Human68k image on this system.**
|
||||
2. MAME's `x68k_hdc` SASI implementation may be too partial for IOCS-level reads.
|
||||
3. `SP=$8000` may put the injected stack on top of the IOCS work area in low RAM.
|
||||
Try a much higher stack.
|
||||
4. The **SCSI path was never tried** — this is the obvious next move and is more
|
||||
relevant to the target anyway:
|
||||
`-exp1 cz6bs1 -hard disk.chd` with `exp1:cz6bs1:scsi:0 harddisk`
|
||||
(`-listmedia` gains a `harddisk` slot accepting .chd/.hd/.hdv/.2mg/.hdi).
|
||||
|
||||
**Honest assessment: this benchmark is NOT on the critical path.** The VQ codec
|
||||
(~30 KB/s) is correct whether SASI does 300 or 600 KB/s. Do not let it block the
|
||||
encoder. Its real value is deciding whether the *simpler* row-span codec could
|
||||
have sufficed.
|
||||
|
||||
Caveat if resumed: MAME idealizes drive seek latency. That's acceptable because the
|
||||
realistic deployment is BlueSCSI/SCSI2SD (SD-backed, no mechanical seek), so what
|
||||
gets measured is the bus/DMAC/controller path — the genuine ceiling. The caveat
|
||||
only bites for a real period spinning drive.
|
||||
|
||||
---
|
||||
|
||||
## Next steps, in priority order
|
||||
|
||||
1. **Build the VQ encoder** (`tools/encoder/`) — 4x4 blocks, per-scene codebook,
|
||||
block delta. Emit sample PNGs for visual evaluation. **The open question is
|
||||
whether VQ softens Bluth's ink linework unacceptably — decide by eye before
|
||||
committing to the architecture.**
|
||||
2. **Full-disc survey** — all 224 streams, not 5s samples, to firm up bitrate
|
||||
(current numbers are +/-30%) and map streams onto the arcade scene graph.
|
||||
3. **Check the SNES project's license**, then evaluate reusing `data/events/`
|
||||
(516 chapters / 29 scenes) as the scene-graph and input-timing layer.
|
||||
4. Resolve the SASI/SCSI target question with the user.
|
||||
5. Optionally unblock the disk benchmark via the SCSI path.
|
||||
6. 68000 player skeleton: CRTC init for 256x192x256c, `movem.l` blitter,
|
||||
ADPCM via HD63450 DMA.
|
||||
|
||||
## Not yet started
|
||||
- Any 68000 player code
|
||||
- ADPCM audio extraction/encoding (MSM6258, 15.6kHz mono, ~7.8 KB/s, ~10MB for 22min)
|
||||
- Disk image packaging / container format
|
||||
- Game logic (scene branching, input windows, death clips)
|
||||
@@ -0,0 +1,92 @@
|
||||
import numpy as np, glob
|
||||
from PIL import Image
|
||||
|
||||
files = sorted(glob.glob("an/f*.png"))
|
||||
rgb = [np.asarray(Image.open(f).convert("RGB")) for f in files]
|
||||
N = len(rgb)
|
||||
print(f"frames={N} size={rgb[0].shape}")
|
||||
|
||||
# --- 1. duplicate / twos detection on the SOURCE ---
|
||||
exact = 0; near = 0; diffs = []
|
||||
for i in range(1, N):
|
||||
d = np.abs(rgb[i].astype(np.int16) - rgb[i-1].astype(np.int16))
|
||||
m = d.mean()
|
||||
diffs.append(m)
|
||||
if m == 0: exact += 1
|
||||
elif m < 1.0: near += 1
|
||||
print(f"\n--- source frame-to-frame (24fps) ---")
|
||||
print(f"exact duplicates : {exact}/{N-1} ({100*exact/(N-1):.1f}%)")
|
||||
print(f"near-dup (<1.0) : {near}/{N-1} ({100*near/(N-1):.1f}%)")
|
||||
print(f"mean abs diff : {np.mean(diffs):.2f}")
|
||||
|
||||
# --- 2. build a per-scene 256-color palette from the whole clip ---
|
||||
sample = np.concatenate([rgb[i].reshape(-1,3) for i in range(0,N,4)])
|
||||
pal_img = Image.fromarray(sample.reshape(-1,1,3).astype(np.uint8))
|
||||
pal = pal_img.quantize(colors=256, method=Image.MEDIANCUT, dither=Image.NONE)
|
||||
palette = pal.getpalette()[:768]
|
||||
ref = Image.new("P", (1,1)); ref.putpalette(palette)
|
||||
|
||||
idx = []
|
||||
for a in rgb:
|
||||
q = Image.fromarray(a).quantize(palette=ref, dither=Image.FLOYDSTEINBERG)
|
||||
idx.append(np.asarray(q, dtype=np.uint8))
|
||||
|
||||
# quantization error
|
||||
err = np.mean([np.abs(np.asarray(Image.fromarray(idx[i]).convert("P")) ) for i in range(0)]) if False else None
|
||||
|
||||
# --- 3. drop duplicate frames -> unique frame stream ---
|
||||
keep = [0]
|
||||
for i in range(1, N):
|
||||
if not np.array_equal(idx[i], idx[keep[-1]]):
|
||||
keep.append(i)
|
||||
print(f"\n--- after 256-color quantize + dedupe ---")
|
||||
print(f"unique frames : {len(keep)}/{N} -> effective {len(keep)/12.0:.1f} fps")
|
||||
|
||||
# --- 4. delta sparsity between consecutive UNIQUE frames ---
|
||||
changed_pct = []
|
||||
for j in range(1, len(keep)):
|
||||
a, b = idx[keep[j-1]], idx[keep[j]]
|
||||
changed_pct.append(100.0*np.count_nonzero(a != b)/a.size)
|
||||
print(f"pixels changed : mean {np.mean(changed_pct):.1f}% median {np.median(changed_pct):.1f}% p90 {np.percentile(changed_pct,90):.1f}% max {np.max(changed_pct):.1f}%")
|
||||
|
||||
# --- 5. estimate compressed size: row-span delta + RLE within span ---
|
||||
def encode_size(a, b, gap=4):
|
||||
total = 0
|
||||
H, W = a.shape
|
||||
for y in range(H):
|
||||
ra, rb = a[y], b[y]
|
||||
diff = np.nonzero(ra != rb)[0]
|
||||
if len(diff) == 0: continue
|
||||
# merge runs separated by < gap
|
||||
spans = []; s = diff[0]; p = diff[0]
|
||||
for x in diff[1:]:
|
||||
if x - p > gap: spans.append((s,p)); s = x
|
||||
p = x
|
||||
spans.append((s,p))
|
||||
total += 2 # row header: y + span count
|
||||
for (s0,e0) in spans:
|
||||
seg = rb[s0:e0+1]
|
||||
total += 2 # x start + length
|
||||
# RLE within segment
|
||||
i2 = 0; cost = 0
|
||||
while i2 < len(seg):
|
||||
run = 1
|
||||
while i2+run < len(seg) and seg[i2+run] == seg[i2] and run < 127: run += 1
|
||||
cost += 2 if run >= 3 else run
|
||||
i2 += run
|
||||
total += cost
|
||||
return total
|
||||
|
||||
sizes = [encode_size(idx[keep[j-1]], idx[keep[j]]) for j in range(1, len(keep))]
|
||||
print(f"\n--- codec estimate (row-span delta + RLE) ---")
|
||||
print(f"delta frame bytes: mean {np.mean(sizes):.0f} median {np.median(sizes):.0f} p90 {np.percentile(sizes,90):.0f} max {np.max(sizes):.0f}")
|
||||
raw = 256*192
|
||||
print(f"vs raw {raw} B/frame -> mean ratio {raw/np.mean(sizes):.1f}:1")
|
||||
|
||||
fps_eff = len(keep)/12.0
|
||||
byterate = np.mean(sizes)*fps_eff
|
||||
print(f"\nstream rate : {byterate/1024:.1f} KB/s")
|
||||
print(f"22 min extrapol. : {byterate*22*60/1048576:.0f} MB video")
|
||||
# cycle cost: ~1 word write per changed pixel, movem amortized
|
||||
mean_changed_px = np.mean(changed_pct)/100*raw
|
||||
print(f"mean changed px : {mean_changed_px:.0f} -> blit ~{mean_changed_px*6.5/1000:.0f}k cycles (budget 833k)")
|
||||
@@ -0,0 +1,61 @@
|
||||
import numpy as np, glob
|
||||
from PIL import Image
|
||||
|
||||
files = sorted(glob.glob("an2/f*.png"))
|
||||
imgs = [Image.open(f) for f in files]
|
||||
print("PIL mode:", imgs[0].mode)
|
||||
idx = [np.asarray(im.convert("P") if im.mode!="P" else im, dtype=np.uint8) for im in imgs]
|
||||
N = len(idx); H,W = idx[0].shape; raw = H*W
|
||||
print(f"frames={N} size={H}x{W} raw={raw} B/frame")
|
||||
|
||||
# --- per-pair change stats on the QUANTIZED stream ---
|
||||
pct = np.array([100.0*np.count_nonzero(idx[i]!=idx[i-1])/raw for i in range(1,N)])
|
||||
print(f"\n--- consecutive change % (24fps, quantized) ---")
|
||||
print(f"mean {pct.mean():.1f} median {np.median(pct):.1f} p10 {np.percentile(pct,10):.1f} p90 {np.percentile(pct,90):.1f} max {pct.max():.1f}")
|
||||
print(f"pairs under 2% changed: {np.count_nonzero(pct<2.0)}/{len(pct)} ({100*np.count_nonzero(pct<2.0)/len(pct):.0f}%)")
|
||||
|
||||
# --- threshold dedupe (twos detection) ---
|
||||
THRESH = 2.0
|
||||
keep=[0]
|
||||
for i in range(1,N):
|
||||
if 100.0*np.count_nonzero(idx[i]!=idx[keep[-1]])/raw >= THRESH:
|
||||
keep.append(i)
|
||||
print(f"\n--- dedupe @ {THRESH}% ---")
|
||||
print(f"unique frames: {len(keep)}/{N} -> effective {len(keep)/12.0:.1f} fps")
|
||||
|
||||
cp = np.array([100.0*np.count_nonzero(idx[keep[j]]!=idx[keep[j-1]])/raw for j in range(1,len(keep))])
|
||||
print(f"unique-pair change %: mean {cp.mean():.1f} median {np.median(cp):.1f} p90 {np.percentile(cp,90):.1f} max {cp.max():.1f}")
|
||||
|
||||
def encode_size(a,b,gap=4):
|
||||
total=0
|
||||
for y in range(a.shape[0]):
|
||||
ra,rb=a[y],b[y]
|
||||
d=np.nonzero(ra!=rb)[0]
|
||||
if len(d)==0: continue
|
||||
spans=[]; s=d[0]; p=d[0]
|
||||
for x in d[1:]:
|
||||
if x-p>gap: spans.append((s,p)); s=x
|
||||
p=x
|
||||
spans.append((s,p))
|
||||
total+=2
|
||||
for s0,e0 in spans:
|
||||
seg=rb[s0:e0+1]; total+=2
|
||||
i2=0
|
||||
while i2<len(seg):
|
||||
r=1
|
||||
while i2+r<len(seg) and seg[i2+r]==seg[i2] and r<127: r+=1
|
||||
total += 2 if r>=3 else r
|
||||
i2+=r
|
||||
return total
|
||||
|
||||
sz=np.array([encode_size(idx[keep[j-1]],idx[keep[j]]) for j in range(1,len(keep))])
|
||||
fps_eff=len(keep)/12.0
|
||||
rate=sz.mean()*fps_eff
|
||||
print(f"\n--- codec estimate ---")
|
||||
print(f"delta bytes: mean {sz.mean():.0f} median {np.median(sz):.0f} p90 {np.percentile(sz,90):.0f} max {sz.max():.0f}")
|
||||
print(f"ratio vs raw: {raw/sz.mean():.1f}:1")
|
||||
print(f"stream rate : {rate/1024:.1f} KB/s")
|
||||
print(f"22min video : {rate*22*60/1048576:.0f} MB")
|
||||
px=cp.mean()/100*raw
|
||||
print(f"blit cost : ~{px*6.5/1000:.0f}k cycles/frame (budget 833k)")
|
||||
print(f"p90 blit : ~{np.percentile(cp,90)/100*raw*6.5/1000:.0f}k cycles")
|
||||
@@ -0,0 +1,11 @@
|
||||
import numpy as np, glob, sys
|
||||
from PIL import Image
|
||||
d=sys.argv[1]
|
||||
f=sorted(glob.glob(f"{d}/f*.png"))
|
||||
a=[np.asarray(Image.open(x).convert("RGB"),dtype=np.int16) for x in f]
|
||||
# noise-tolerant per-pair change: % of pixels differing by more than 8 levels
|
||||
p=np.array([100.0*np.count_nonzero(np.abs(a[i]-a[i-1]).max(axis=2)>8)/(a[0].shape[0]*a[0].shape[1]) for i in range(1,len(a))])
|
||||
print(f" pairs={len(p)} mean={p.mean():.2f}% median={np.median(p):.2f}% p90={np.percentile(p,90):.2f}% max={p.max():.2f}%")
|
||||
ev,od=p[0::2],p[1::2]
|
||||
print(f" even-idx pairs mean={ev.mean():.2f}% odd-idx pairs mean={od.mean():.2f}% ratio={max(ev.mean(),od.mean())/max(min(ev.mean(),od.mean()),1e-9):.1f}x")
|
||||
print(f" first 16 pairs: {np.round(p[:16],2)}")
|
||||
@@ -0,0 +1,54 @@
|
||||
import numpy as np, glob, os
|
||||
from PIL import Image
|
||||
|
||||
def enc(a,b,gap=4):
|
||||
t=0
|
||||
for y in range(a.shape[0]):
|
||||
ra,rb=a[y],b[y]; d=np.nonzero(ra!=rb)[0]
|
||||
if not len(d): continue
|
||||
sp=[]; s=d[0]; p=d[0]
|
||||
for x in d[1:]:
|
||||
if x-p>gap: sp.append((s,p)); s=x
|
||||
p=x
|
||||
sp.append((s,p)); t+=2
|
||||
for s0,e0 in sp:
|
||||
seg=rb[s0:e0+1]; t+=2; i=0
|
||||
while i<len(seg):
|
||||
r=1
|
||||
while i+r<len(seg) and seg[i+r]==seg[i] and r<127: r+=1
|
||||
t += 2 if r>=3 else r; i+=r
|
||||
return t
|
||||
|
||||
RAW=256*192; rows=[]
|
||||
for d in sorted(glob.glob("samp/*")):
|
||||
s=os.path.basename(d)
|
||||
fs=sorted(glob.glob(f"{d}/f*.png"))
|
||||
if len(fs)<10: continue
|
||||
rgb=[np.asarray(Image.open(x).convert("RGB")) for x in fs]
|
||||
# one shared palette per scene, no dither (cel art is flat)
|
||||
samp=np.concatenate([r.reshape(-1,3) for r in rgb[::3]])
|
||||
ref=Image.fromarray(samp.reshape(-1,1,3)).quantize(colors=256,method=Image.MEDIANCUT,dither=Image.NONE)
|
||||
idx=[np.asarray(Image.fromarray(r).quantize(palette=ref,dither=Image.NONE),dtype=np.uint8) for r in rgb]
|
||||
N=len(idx)
|
||||
# dedupe @0.3%
|
||||
keep=[0]
|
||||
for i in range(1,N):
|
||||
if 100.0*np.count_nonzero(idx[i]!=idx[keep[-1]])/RAW >= 0.3: keep.append(i)
|
||||
fps=len(keep)/5.0
|
||||
cp=np.array([100.0*np.count_nonzero(idx[keep[j]]!=idx[keep[j-1]])/RAW for j in range(1,len(keep))])
|
||||
sz=np.array([enc(idx[keep[j-1]],idx[keep[j]]) for j in range(1,len(keep))])
|
||||
rows.append((s,N,len(keep),fps,cp.mean(),np.percentile(cp,90),sz.mean(),np.percentile(sz,90),sz.mean()*fps))
|
||||
print(f"{s}: src={N} uniq={len(keep)} fps={fps:4.1f} chg={cp.mean():5.1f}%/p90 {np.percentile(cp,90):5.1f}% delta={sz.mean():6.0f}B/p90 {np.percentile(sz,90):6.0f} rate={sz.mean()*fps/1024:6.1f}KB/s")
|
||||
|
||||
r=np.array([x[3:] for x in rows],dtype=float)
|
||||
print("\n=== AGGREGATE (8 scenes) ===")
|
||||
print(f"effective fps after dedupe : {r[:,0].mean():.1f}")
|
||||
print(f"pixels changed / frame : mean {r[:,1].mean():.1f}% p90 {r[:,2].mean():.1f}%")
|
||||
print(f"delta frame size : mean {r[:,3].mean():.0f} B p90 {r[:,4].mean():.0f} B")
|
||||
print(f"compression vs raw : {RAW/r[:,3].mean():.1f}:1")
|
||||
rate=r[:,5].mean()
|
||||
print(f"stream rate : {rate/1024:.0f} KB/s")
|
||||
print(f"22 min of video : {rate*22*60/1048576:.0f} MB")
|
||||
px=r[:,1].mean()/100*RAW
|
||||
print(f"blit cost mean : ~{px*6.5/1000:.0f}k cycles (budget 833k @12fps)")
|
||||
print(f"blit cost p90 : ~{r[:,2].mean()/100*RAW*6.5/1000:.0f}k cycles")
|
||||
@@ -0,0 +1,8 @@
|
||||
local function s(x) return tostring(x) end
|
||||
print("[API] add_machine_frame_notifier = "..s(emu.add_machine_frame_notifier))
|
||||
print("[API] add_machine_periodic_notifier = "..s(emu.add_machine_periodic_notifier))
|
||||
print("[API] add_machine_reset_notifier = "..s(emu.add_machine_reset_notifier))
|
||||
local m=manager.machine
|
||||
print("[API] time.seconds="..s(m.time.seconds).." atto="..s(m.time.attoseconds))
|
||||
print("[API] machine.exit="..s(m.exit))
|
||||
for k,v in pairs(emu) do if tostring(k):find("notif") then print("[API] emu."..s(k)) end end
|
||||
@@ -0,0 +1,53 @@
|
||||
M = manager.machine
|
||||
CPU = M.devices[":maincpu"]
|
||||
SP = CPU.spaces["program"]
|
||||
FLAG,STAT,NREAD,BUF = 0x18000,0x18004,0x18008,0x20000
|
||||
TOTAL = 16*65536
|
||||
local f=io.open("bench.bin","rb"); local d=f:read("a"); f:close()
|
||||
CODE={} ; for i=1,#d do CODE[i]=string.byte(d,i) end
|
||||
print("[BENCH] loaded bench.bin bytes="..#d)
|
||||
STATE,T0,NFIRE = "wait",nil,0
|
||||
local function T() local t=M.time return t.seconds + t.attoseconds/1e18 end
|
||||
local function P(s) print("[BENCH] "..s) end
|
||||
|
||||
local function tick()
|
||||
NFIRE = NFIRE + 1
|
||||
if NFIRE==1 then P("notifier firing OK") end
|
||||
local t=T()
|
||||
if STATE=="wait" then
|
||||
if t < 5.0 then return end
|
||||
for i=1,#CODE do SP:write_u8(0x10000+i-1, CODE[i]) end
|
||||
SP:write_u32(FLAG,0); SP:write_u32(STAT,0); SP:write_u32(NREAD,0); SP:write_u32(BUF,0)
|
||||
CPU.state["SR"].value=0x2000
|
||||
CPU.state["SP"].value=0x8000
|
||||
CPU.state["PC"].value=0x10000
|
||||
STATE="run"
|
||||
P(string.format("injected t=%.3f frames=%d",t,NFIRE))
|
||||
return
|
||||
end
|
||||
if STATE=="run" then
|
||||
local fl=SP:read_u32(FLAG)
|
||||
if fl==1 and not T0 then T0=t; P(string.format("started t=%.4f",t)) end
|
||||
if fl==0xFF then
|
||||
STATE="done"
|
||||
local dt=t-(T0 or t)
|
||||
P(string.format("DONE reads=%d status=%08X",SP:read_u32(NREAD),SP:read_u32(STAT)))
|
||||
P(string.format("elapsed=%.4f s bytes=%d",dt,TOTAL))
|
||||
if dt>0 then P(string.format("THROUGHPUT = %.1f KB/s",TOTAL/1024/dt)) end
|
||||
P(string.format("buf=%08X %08X",SP:read_u32(BUF),SP:read_u32(BUF+4)))
|
||||
M:exit()
|
||||
elseif fl==0xEE then
|
||||
STATE="done"; P(string.format("READ FAILED status=%08X reads=%d",SP:read_u32(STAT),SP:read_u32(NREAD))); M:exit()
|
||||
elseif t>25 then
|
||||
STATE="done"
|
||||
P(string.format("TIMEOUT flag=%08X status=%08X reads=%d PC=%08X SR=%04X",
|
||||
fl,SP:read_u32(STAT),SP:read_u32(NREAD),CPU.state["PC"].value,CPU.state["SR"].value))
|
||||
M:exit()
|
||||
end
|
||||
end
|
||||
end
|
||||
SUB = emu.add_machine_frame_notifier(function()
|
||||
local ok,err = pcall(tick)
|
||||
if not ok then print("[BENCH] LUA ERROR: "..tostring(err)); M:exit() end
|
||||
end)
|
||||
print("[BENCH] subscription="..tostring(SUB))
|
||||
@@ -0,0 +1,36 @@
|
||||
FLAG = $18000
|
||||
STAT = $18004
|
||||
NREAD = $18008
|
||||
BUF = $20000
|
||||
CHUNK = 65536 ; bytes per read
|
||||
RECS = CHUNK/256 ; 256-byte records per read
|
||||
NCHUNK = 16 ; 16 x 64KB = 1MB total
|
||||
|
||||
org $10000
|
||||
start:
|
||||
move.l #1,FLAG.l ; signal: started
|
||||
move.l #NCHUNK,d6
|
||||
moveq #0,d5 ; record position
|
||||
moveq #0,d4 ; completed count
|
||||
loop:
|
||||
movem.l d4-d6,-(sp)
|
||||
moveq #$46,d0 ; _B_READ
|
||||
move.l #$80000000,d1 ; PDA=$80 (SASI unit0), mode=0
|
||||
move.l d5,d2 ; position (records)
|
||||
move.l #CHUNK,d3 ; byte count
|
||||
lea BUF,a1
|
||||
trap #15
|
||||
movem.l (sp)+,d4-d6
|
||||
move.l d0,STAT.l ; last status
|
||||
tst.l d0
|
||||
bmi.s failed
|
||||
addq.l #1,d4
|
||||
move.l d4,NREAD.l
|
||||
add.l #RECS,d5
|
||||
subq.l #1,d6
|
||||
bne.s loop
|
||||
move.l #$FF,FLAG.l ; signal: done OK
|
||||
stop: bra.s stop
|
||||
failed:
|
||||
move.l #$EE,FLAG.l ; signal: error
|
||||
bra.s stop
|
||||
@@ -0,0 +1,24 @@
|
||||
M=manager.machine; CPU=M.devices[":maincpu"]; SP=CPU.spaces["program"]
|
||||
local f=io.open("one.bin","rb"); local d=f:read("a"); f:close()
|
||||
CODE={}; for i=1,#d do CODE[i]=string.byte(d,i) end
|
||||
STATE="wait"
|
||||
local function T() local t=M.time return t.seconds+t.attoseconds/1e18 end
|
||||
local function tick()
|
||||
local t=T()
|
||||
if STATE=="wait" then
|
||||
if t<5.0 then return end
|
||||
for i=1,#CODE do SP:write_u8(0x10000+i-1,CODE[i]) end
|
||||
SP:write_u32(0x18000,0); SP:write_u32(0x18004,0x5A5A5A5A); SP:write_u32(0x20000,0)
|
||||
CPU.state["SR"].value=0x2000; CPU.state["SP"].value=0x8000; CPU.state["PC"].value=0x10000
|
||||
STATE="run"; return
|
||||
end
|
||||
local fl=SP:read_u32(0x18000)
|
||||
if fl==0xFF or t>20 then
|
||||
STATE="done"
|
||||
print(string.format("[ONE] %s status=%08X buf=%08X",
|
||||
os.getenv("SZLABEL") or "?", SP:read_u32(0x18004), SP:read_u32(0x20000)))
|
||||
M:exit()
|
||||
end
|
||||
end
|
||||
SUB=emu.add_machine_frame_notifier(function()
|
||||
local ok,e=pcall(tick); if not ok then print("[ONE] ERR "..tostring(e)); M:exit() end end)
|
||||
@@ -0,0 +1,14 @@
|
||||
FLAG=$18000
|
||||
STAT=$18004
|
||||
BUF=$20000
|
||||
org $10000
|
||||
start: move.l #1,FLAG.l
|
||||
moveq #$46,d0
|
||||
move.l #$80000000,d1
|
||||
moveq #0,d2
|
||||
move.l #256,d3
|
||||
lea BUF,a1
|
||||
trap #15
|
||||
move.l d0,STAT.l
|
||||
move.l #$FF,FLAG.l
|
||||
stop: bra.s stop
|
||||
@@ -0,0 +1,5 @@
|
||||
local cpu = manager.machine.devices[":maincpu"]
|
||||
local names={}
|
||||
for k,v in pairs(cpu.state) do names[#names+1]=tostring(k) end
|
||||
table.sort(names)
|
||||
print("[REGS] "..table.concat(names,", "))
|
||||
@@ -0,0 +1,32 @@
|
||||
M=manager.machine; CPU=M.devices[":maincpu"]; SP=CPU.spaces["program"]
|
||||
local f=io.open("sweep.bin","rb"); local d=f:read("a"); f:close()
|
||||
CODE={}; for i=1,#d do CODE[i]=string.byte(d,i) end
|
||||
STATE="wait"
|
||||
local function T() local t=M.time return t.seconds+t.attoseconds/1e18 end
|
||||
local function tick()
|
||||
local t=T()
|
||||
if STATE=="wait" then
|
||||
if t<5.0 then return end
|
||||
for i=1,#CODE do SP:write_u8(0x10000+i-1,CODE[i]) end
|
||||
SP:write_u32(0x18000,0)
|
||||
for i=0,31 do SP:write_u32(0x18100+i*4,0x5A5A5A5A) end
|
||||
CPU.state["SR"].value=0x2000; CPU.state["SP"].value=0x8000; CPU.state["PC"].value=0x10000
|
||||
STATE="run"; print("[SW] injected")
|
||||
return
|
||||
end
|
||||
if STATE=="run" then
|
||||
local fl=SP:read_u32(0x18000)
|
||||
if fl==0xFF or t>25 then
|
||||
STATE="done"
|
||||
print("[SW] flag="..string.format("%X",fl))
|
||||
for i=0,15 do
|
||||
local a=SP:read_u32(0x18100+i*8)
|
||||
local b=SP:read_u32(0x18100+i*8+4)
|
||||
print(string.format("[SW] PDA=%02X encA(<<24)=%08X encB(<<8)=%08X",0x80+i,a,b))
|
||||
end
|
||||
M:exit()
|
||||
end
|
||||
end
|
||||
end
|
||||
SUB=emu.add_machine_frame_notifier(function()
|
||||
local ok,err=pcall(tick); if not ok then print("[SW] ERR "..tostring(err)); M:exit() end end)
|
||||
@@ -0,0 +1,34 @@
|
||||
FLAG = $18000
|
||||
RESULT = $18100
|
||||
BUF = $20000
|
||||
org $10000
|
||||
start:
|
||||
move.l #1,FLAG.l
|
||||
lea RESULT,a2
|
||||
moveq #0,d4
|
||||
outer:
|
||||
move.l d4,d1 ; encoding A: PDA in bits 31-24
|
||||
add.l #$80,d1
|
||||
swap d1
|
||||
lsl.l #8,d1
|
||||
bsr.s doread
|
||||
move.l d0,(a2)+
|
||||
move.l d4,d1 ; encoding B: PDA in bits 15-8
|
||||
add.l #$80,d1
|
||||
lsl.l #8,d1
|
||||
bsr.s doread
|
||||
move.l d0,(a2)+
|
||||
addq.l #1,d4
|
||||
cmpi.l #16,d4
|
||||
bne.s outer
|
||||
move.l #$FF,FLAG.l
|
||||
stop: bra.s stop
|
||||
doread:
|
||||
movem.l d2-d7/a2-a6,-(sp)
|
||||
moveq #$46,d0
|
||||
moveq #0,d2
|
||||
move.l #256,d3
|
||||
lea BUF,a1
|
||||
trap #15
|
||||
movem.l (sp)+,d2-d7/a2-a6
|
||||
rts
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user