c64-kb 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2082 -0
- package/LICENSE +29 -0
- package/README.md +559 -0
- package/VERSION +25 -0
- package/dist/cli/setup.js +66 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli-memorize-audit.js +97 -0
- package/dist/cli-memorize-audit.js.map +1 -0
- package/dist/cli.js +335 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +83 -0
- package/dist/config.js.map +1 -0
- package/dist/context.js +53 -0
- package/dist/context.js.map +1 -0
- package/dist/domain/budget-notes.js +104 -0
- package/dist/domain/budget-notes.js.map +1 -0
- package/dist/domain/budget.js +417 -0
- package/dist/domain/budget.js.map +1 -0
- package/dist/domain/game-design.js +77 -0
- package/dist/domain/game-design.js.map +1 -0
- package/dist/domain/timing.js +39 -0
- package/dist/domain/timing.js.map +1 -0
- package/dist/graph/apply.js +132 -0
- package/dist/graph/apply.js.map +1 -0
- package/dist/graph/claims.js +231 -0
- package/dist/graph/claims.js.map +1 -0
- package/dist/graph/extract/archetype.js +118 -0
- package/dist/graph/extract/archetype.js.map +1 -0
- package/dist/graph/extract/common.js +69 -0
- package/dist/graph/extract/common.js.map +1 -0
- package/dist/graph/extract/failure.js +37 -0
- package/dist/graph/extract/failure.js.map +1 -0
- package/dist/graph/extract/game-design.js +189 -0
- package/dist/graph/extract/game-design.js.map +1 -0
- package/dist/graph/extract/hardware.js +124 -0
- package/dist/graph/extract/hardware.js.map +1 -0
- package/dist/graph/extract/pitfall.js +62 -0
- package/dist/graph/extract/pitfall.js.map +1 -0
- package/dist/graph/extract/raster-band.js +56 -0
- package/dist/graph/extract/raster-band.js.map +1 -0
- package/dist/graph/extract/recipe.js +71 -0
- package/dist/graph/extract/recipe.js.map +1 -0
- package/dist/graph/extract/technique-entities.js +180 -0
- package/dist/graph/extract/technique-entities.js.map +1 -0
- package/dist/graph/extract/technique.js +179 -0
- package/dist/graph/extract/technique.js.map +1 -0
- package/dist/graph/extract/toolchain.js +67 -0
- package/dist/graph/extract/toolchain.js.map +1 -0
- package/dist/graph/extract/types.js +3 -0
- package/dist/graph/extract/types.js.map +1 -0
- package/dist/graph/extract/vocabulary.js +65 -0
- package/dist/graph/extract/vocabulary.js.map +1 -0
- package/dist/graph/extract.js +43 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/kernal-clobbers.js +64 -0
- package/dist/graph/kernal-clobbers.js.map +1 -0
- package/dist/graph/machine-variants.js +208 -0
- package/dist/graph/machine-variants.js.map +1 -0
- package/dist/ingest/files.js +99 -0
- package/dist/ingest/files.js.map +1 -0
- package/dist/ingest/passes.js +104 -0
- package/dist/ingest/passes.js.map +1 -0
- package/dist/ingest/points.js +50 -0
- package/dist/ingest/points.js.map +1 -0
- package/dist/ingest/report.js +140 -0
- package/dist/ingest/report.js.map +1 -0
- package/dist/ingest/tally.js +81 -0
- package/dist/ingest/tally.js.map +1 -0
- package/dist/ingest/verified-on.js +43 -0
- package/dist/ingest/verified-on.js.map +1 -0
- package/dist/ingest.js +135 -0
- package/dist/ingest.js.map +1 -0
- package/dist/schemas/cost-basis.js +5 -0
- package/dist/schemas/cost-basis.js.map +1 -0
- package/dist/schemas/machine-variant.js +25 -0
- package/dist/schemas/machine-variant.js.map +1 -0
- package/dist/schemas/plan-budget.js +121 -0
- package/dist/schemas/plan-budget.js.map +1 -0
- package/dist/schemas/tool-outputs.js +526 -0
- package/dist/schemas/tool-outputs.js.map +1 -0
- package/dist/server/define-tool.js +52 -0
- package/dist/server/define-tool.js.map +1 -0
- package/dist/server/defined-only.js +9 -0
- package/dist/server/defined-only.js.map +1 -0
- package/dist/server/tools-briefings.js +70 -0
- package/dist/server/tools-briefings.js.map +1 -0
- package/dist/server/tools-lookup.js +180 -0
- package/dist/server/tools-lookup.js.map +1 -0
- package/dist/server/tools-maintenance.js +116 -0
- package/dist/server/tools-maintenance.js.map +1 -0
- package/dist/server/tools-pitfalls.js +89 -0
- package/dist/server/tools-pitfalls.js.map +1 -0
- package/dist/server/tools-recipes.js +296 -0
- package/dist/server/tools-recipes.js.map +1 -0
- package/dist/server/tools-runtime.js +48 -0
- package/dist/server/tools-runtime.js.map +1 -0
- package/dist/server/tools.js +43 -0
- package/dist/server/tools.js.map +1 -0
- package/dist/server.js +95 -0
- package/dist/server.js.map +1 -0
- package/dist/services/analytics/schema.js +78 -0
- package/dist/services/analytics/schema.js.map +1 -0
- package/dist/services/analytics/statements.js +99 -0
- package/dist/services/analytics/statements.js.map +1 -0
- package/dist/services/analytics.js +195 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/bm25.js +158 -0
- package/dist/services/bm25.js.map +1 -0
- package/dist/services/chunker.js +81 -0
- package/dist/services/chunker.js.map +1 -0
- package/dist/services/embeddings.js +58 -0
- package/dist/services/embeddings.js.map +1 -0
- package/dist/services/falkor/base.js +85 -0
- package/dist/services/falkor/base.js.map +1 -0
- package/dist/services/falkor/links.js +361 -0
- package/dist/services/falkor/links.js.map +1 -0
- package/dist/services/falkor/nodes.js +183 -0
- package/dist/services/falkor/nodes.js.map +1 -0
- package/dist/services/falkor/params.js +45 -0
- package/dist/services/falkor/params.js.map +1 -0
- package/dist/services/falkor/schema.js +77 -0
- package/dist/services/falkor/schema.js.map +1 -0
- package/dist/services/falkor.js +100 -0
- package/dist/services/falkor.js.map +1 -0
- package/dist/services/qdrant.js +238 -0
- package/dist/services/qdrant.js.map +1 -0
- package/dist/services/versions.js +73 -0
- package/dist/services/versions.js.map +1 -0
- package/dist/services/vice-bin.js +51 -0
- package/dist/services/vice-bin.js.map +1 -0
- package/dist/tools/briefings/archetype.js +174 -0
- package/dist/tools/briefings/archetype.js.map +1 -0
- package/dist/tools/briefings/budget.js +177 -0
- package/dist/tools/briefings/budget.js.map +1 -0
- package/dist/tools/briefings/build-order.js +135 -0
- package/dist/tools/briefings/build-order.js.map +1 -0
- package/dist/tools/briefings/build.js +185 -0
- package/dist/tools/briefings/build.js.map +1 -0
- package/dist/tools/briefings/discovery.js +290 -0
- package/dist/tools/briefings/discovery.js.map +1 -0
- package/dist/tools/briefings/plan-pitfalls.js +69 -0
- package/dist/tools/briefings/plan-pitfalls.js.map +1 -0
- package/dist/tools/briefings/render.js +151 -0
- package/dist/tools/briefings/render.js.map +1 -0
- package/dist/tools/briefings/rows.js +41 -0
- package/dist/tools/briefings/rows.js.map +1 -0
- package/dist/tools/briefings/toolchain.js +63 -0
- package/dist/tools/briefings/toolchain.js.map +1 -0
- package/dist/tools/briefings/why-proposed.js +69 -0
- package/dist/tools/briefings/why-proposed.js.map +1 -0
- package/dist/tools/briefings.js +44 -0
- package/dist/tools/briefings.js.map +1 -0
- package/dist/tools/feedback.js +97 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/hydrate.js +75 -0
- package/dist/tools/hydrate.js.map +1 -0
- package/dist/tools/intelligence.js +67 -0
- package/dist/tools/intelligence.js.map +1 -0
- package/dist/tools/lint/asm-decimal.js +86 -0
- package/dist/tools/lint/asm-decimal.js.map +1 -0
- package/dist/tools/lint/asm-rules.js +221 -0
- package/dist/tools/lint/asm-rules.js.map +1 -0
- package/dist/tools/lint/asm-shared.js +3 -0
- package/dist/tools/lint/asm-shared.js.map +1 -0
- package/dist/tools/lint/c-rules.js +215 -0
- package/dist/tools/lint/c-rules.js.map +1 -0
- package/dist/tools/lint/text.js +122 -0
- package/dist/tools/lint/text.js.map +1 -0
- package/dist/tools/lint/types.js +40 -0
- package/dist/tools/lint/types.js.map +1 -0
- package/dist/tools/lint.js +76 -0
- package/dist/tools/lint.js.map +1 -0
- package/dist/tools/memorization-mcp.js +111 -0
- package/dist/tools/memorization-mcp.js.map +1 -0
- package/dist/tools/pitfalls/bm25.js +53 -0
- package/dist/tools/pitfalls/bm25.js.map +1 -0
- package/dist/tools/pitfalls/format.js +58 -0
- package/dist/tools/pitfalls/format.js.map +1 -0
- package/dist/tools/pitfalls/graph.js +124 -0
- package/dist/tools/pitfalls/graph.js.map +1 -0
- package/dist/tools/pitfalls.js +134 -0
- package/dist/tools/pitfalls.js.map +1 -0
- package/dist/tools/prompts.js +81 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/query/compatibility/closure.js +62 -0
- package/dist/tools/query/compatibility/closure.js.map +1 -0
- package/dist/tools/query/compatibility/facts.js +28 -0
- package/dist/tools/query/compatibility/facts.js.map +1 -0
- package/dist/tools/query/compatibility/fetch.js +164 -0
- package/dist/tools/query/compatibility/fetch.js.map +1 -0
- package/dist/tools/query/compatibility/hard-rules.js +159 -0
- package/dist/tools/query/compatibility/hard-rules.js.map +1 -0
- package/dist/tools/query/compatibility/index.js +27 -0
- package/dist/tools/query/compatibility/index.js.map +1 -0
- package/dist/tools/query/compatibility/kernal-zp-rule.js +90 -0
- package/dist/tools/query/compatibility/kernal-zp-rule.js.map +1 -0
- package/dist/tools/query/compatibility/render.js +106 -0
- package/dist/tools/query/compatibility/render.js.map +1 -0
- package/dist/tools/query/compatibility/rules.js +337 -0
- package/dist/tools/query/compatibility/rules.js.map +1 -0
- package/dist/tools/query/compatibility/unit-rules.js +253 -0
- package/dist/tools/query/compatibility/unit-rules.js.map +1 -0
- package/dist/tools/query/game-design.js +83 -0
- package/dist/tools/query/game-design.js.map +1 -0
- package/dist/tools/query/lookups.js +220 -0
- package/dist/tools/query/lookups.js.map +1 -0
- package/dist/tools/query/plan-budget.js +266 -0
- package/dist/tools/query/plan-budget.js.map +1 -0
- package/dist/tools/query/recipes.js +214 -0
- package/dist/tools/query/recipes.js.map +1 -0
- package/dist/tools/query/retrieval.js +128 -0
- package/dist/tools/query/retrieval.js.map +1 -0
- package/dist/tools/query/shared.js +121 -0
- package/dist/tools/query/shared.js.map +1 -0
- package/dist/tools/query/techniques.js +331 -0
- package/dist/tools/query/techniques.js.map +1 -0
- package/dist/tools/query/timing.js +106 -0
- package/dist/tools/query/timing.js.map +1 -0
- package/dist/tools/query/types.js +11 -0
- package/dist/tools/query/types.js.map +1 -0
- package/dist/tools/query.js +21 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/resources.js +186 -0
- package/dist/tools/resources.js.map +1 -0
- package/dist/tools/run-game.js +249 -0
- package/dist/tools/run-game.js.map +1 -0
- package/dist/tools/selfimprovement.js +367 -0
- package/dist/tools/selfimprovement.js.map +1 -0
- package/docker-compose.yml +35 -0
- package/docs/ARCHITECTURE.md +164 -0
- package/docs/CONVENTIONS-archetypes.md +158 -0
- package/docs/CONVENTIONS-failures.md +46 -0
- package/docs/CONVENTIONS-game-designs.md +75 -0
- package/docs/CONVENTIONS-hardware-reference.md +184 -0
- package/docs/CONVENTIONS-pitfalls.md +91 -0
- package/docs/CONVENTIONS-recipes.md +106 -0
- package/docs/CONVENTIONS-techniques.md +309 -0
- package/docs/CONVENTIONS-toolchain-reference.md +89 -0
- package/docs/ONTOLOGY.md +655 -0
- package/docs/art/art-production-reference.md +157 -0
- package/docs/art/asset-pipelines.md +586 -0
- package/docs/c64-failure-patterns.md +693 -0
- package/docs/demo-design/demo-composition.md +349 -0
- package/docs/demo-design/demo-design-philosophy.md +320 -0
- package/docs/demo-design/intro-cracktro-patterns.md +435 -0
- package/docs/figures/ctm-spd-embed-probe.png +0 -0
- package/docs/figures/dot-flag-mid-5000000.png +0 -0
- package/docs/figures/dot-flag-no-erase-12000000.png +0 -0
- package/docs/figures/edges.json +1 -0
- package/docs/figures/fig6-architecture.png +0 -0
- package/docs/figures/fig7-ontology.png +0 -0
- package/docs/figures/screen-dissolve-mid-frame25.png +0 -0
- package/docs/figures/screen-dissolve-wipe-frame25.png +0 -0
- package/docs/figures/spindle-two-parts-part1.png +0 -0
- package/docs/figures/spindle-two-parts-part2.png +0 -0
- package/docs/figures/starters/action-puzzle.png +0 -0
- package/docs/figures/starters/adventure.png +0 -0
- package/docs/figures/starters/beat-em-up.png +0 -0
- package/docs/figures/starters/demo.png +0 -0
- package/docs/figures/starters/platformer.png +0 -0
- package/docs/figures/starters/shmup-vertical.png +0 -0
- package/docs/figures/tape-kernal-master-ntsc.png +0 -0
- package/docs/figures/tape-kernal-master-pal.png +0 -0
- package/docs/figures/tape-turbo-loader-ntsc.png +0 -0
- package/docs/figures/tape-turbo-loader-pal.png +0 -0
- package/docs/figures/tape-turbo-master-ntsc.png +0 -0
- package/docs/figures/tape-turbo-master-pal.png +0 -0
- package/docs/figures/tech-tech-pad2-12000000.png +0 -0
- package/docs/figures/tech-tech-pad4-12000000.png +0 -0
- package/docs/figures/tech-tech-seven-12000000.png +0 -0
- package/docs/figures/twister-mid-5000000.png +0 -0
- package/docs/figures/twister-straight-12000000.png +0 -0
- package/docs/figures/vector-balls-nosort-ntsc.png +0 -0
- package/docs/figures/vector-balls-nosort.png +0 -0
- package/docs/formats/c64-file-formats.md +1212 -0
- package/docs/formats/iec-disk-reference.md +754 -0
- package/docs/game-design/c64-game-archetypes.md +273 -0
- package/docs/game-design/designs/falling-blocks.md +54 -0
- package/docs/game-design/designs/platformer-scaffold.md +48 -0
- package/docs/game-design/designs/simple-shmup.md +27 -0
- package/docs/game-design/enemy-behaviour-and-difficulty.md +349 -0
- package/docs/game-design/game-design-patterns.md +1189 -0
- package/docs/game-design/game-structure.md +354 -0
- package/docs/game-design/production-planning.md +349 -0
- package/docs/game-design/reference-game-sources.md +90 -0
- package/docs/hardware/6502-illegal-opcodes.md +1353 -0
- package/docs/hardware/6510-cpu-reference.md +2112 -0
- package/docs/hardware/c64-memory-map.md +3184 -0
- package/docs/hardware/c64-registers-reference.md +996 -0
- package/docs/hardware/cia-reference.md +1281 -0
- package/docs/hardware/kernal-routines-reference.md +1836 -0
- package/docs/hardware/pal-ntsc-reference.md +813 -0
- package/docs/hardware/sid-reference.md +1137 -0
- package/docs/hardware/vic-ii-reference.md +1714 -0
- package/docs/music/music-production-reference.md +403 -0
- package/docs/pitfalls/banking.md +1067 -0
- package/docs/pitfalls/cia.md +1147 -0
- package/docs/pitfalls/cpu.md +874 -0
- package/docs/pitfalls/input.md +282 -0
- package/docs/pitfalls/kernal-and-io.md +1615 -0
- package/docs/pitfalls/loader.md +1191 -0
- package/docs/pitfalls/logic.md +94 -0
- package/docs/pitfalls/maths.md +187 -0
- package/docs/pitfalls/raster-and-badline.md +721 -0
- package/docs/pitfalls/region-timing.md +660 -0
- package/docs/pitfalls/scroll.md +207 -0
- package/docs/pitfalls/sid.md +738 -0
- package/docs/pitfalls/sprite.md +979 -0
- package/docs/pitfalls/text-mode-render.md +1172 -0
- package/docs/recipes/cc65/cartridge-8k.md +398 -0
- package/docs/recipes/cc65/hello-world-conio.md +76 -0
- package/docs/recipes/cc65/memory-layout.md +312 -0
- package/docs/recipes/cc65/screenshots/README.md +14 -0
- package/docs/recipes/cc65/screenshots/cartridge-8k-ntsc.png +0 -0
- package/docs/recipes/cc65/screenshots/cartridge-8k.png +0 -0
- package/docs/recipes/cc65/screenshots/hello-world-conio.png +0 -0
- package/docs/recipes/cc65/screenshots/memory-layout-ntsc.png +0 -0
- package/docs/recipes/cc65/screenshots/memory-layout.png +0 -0
- package/docs/recipes/kickassembler/basic-float-calls.md +578 -0
- package/docs/recipes/kickassembler/basic-wedge.md +398 -0
- package/docs/recipes/kickassembler/big-font-scroller.md +620 -0
- package/docs/recipes/kickassembler/charset-copy-rom-to-ram.md +623 -0
- package/docs/recipes/kickassembler/cia-revision-detect.md +366 -0
- package/docs/recipes/kickassembler/colour-cycling.md +343 -0
- package/docs/recipes/kickassembler/colour-fade.md +303 -0
- package/docs/recipes/kickassembler/compare-16bit-signed.md +1007 -0
- package/docs/recipes/kickassembler/cracktro-template.md +526 -0
- package/docs/recipes/kickassembler/crt-banked.md +276 -0
- package/docs/recipes/kickassembler/dos-error-codes.md +466 -0
- package/docs/recipes/kickassembler/dot-flag.md +576 -0
- package/docs/recipes/kickassembler/drive-job-queue.md +667 -0
- package/docs/recipes/kickassembler/drive-via-probe.md +690 -0
- package/docs/recipes/kickassembler/dycp-scroller.md +568 -0
- package/docs/recipes/kickassembler/dypp-sprite-scroller.md +681 -0
- package/docs/recipes/kickassembler/dysp.md +670 -0
- package/docs/recipes/kickassembler/easyflash-save.md +800 -0
- package/docs/recipes/kickassembler/eight-way-scroll.md +1484 -0
- package/docs/recipes/kickassembler/file-io-roundtrip.md +546 -0
- package/docs/recipes/kickassembler/fire-effect.md +420 -0
- package/docs/recipes/kickassembler/fld.md +414 -0
- package/docs/recipes/kickassembler/fli-image.md +336 -0
- package/docs/recipes/kickassembler/four-player-read.md +672 -0
- package/docs/recipes/kickassembler/headless-verify.md +228 -0
- package/docs/recipes/kickassembler/hello-world.md +98 -0
- package/docs/recipes/kickassembler/high-score-insert.md +660 -0
- package/docs/recipes/kickassembler/hires-plot-line.md +723 -0
- package/docs/recipes/kickassembler/irq-chain.md +316 -0
- package/docs/recipes/kickassembler/irq-owns-port.md +668 -0
- package/docs/recipes/kickassembler/isometric-room.md +1145 -0
- package/docs/recipes/kickassembler/light-pen-read.md +926 -0
- package/docs/recipes/kickassembler/logic-rate-decoupling.md +670 -0
- package/docs/recipes/kickassembler/mci-interlace.md +543 -0
- package/docs/recipes/kickassembler/memory-layout.md +277 -0
- package/docs/recipes/kickassembler/mouse-1351-read.md +801 -0
- package/docs/recipes/kickassembler/nmi-timer-tick.md +562 -0
- package/docs/recipes/kickassembler/own-keyscan.md +881 -0
- package/docs/recipes/kickassembler/paddle-read.md +751 -0
- package/docs/recipes/kickassembler/palette-cells.md +219 -0
- package/docs/recipes/kickassembler/pseudo-3d-road.md +930 -0
- package/docs/recipes/kickassembler/pwm-digi.md +463 -0
- package/docs/recipes/kickassembler/raster-bars.md +264 -0
- package/docs/recipes/kickassembler/reu-dma.md +1019 -0
- package/docs/recipes/kickassembler/runtime-relocation.md +743 -0
- package/docs/recipes/kickassembler/screen-dissolve.md +545 -0
- package/docs/recipes/kickassembler/screen-wipe.md +408 -0
- package/docs/recipes/kickassembler/screenshots/README.md +72 -0
- package/docs/recipes/kickassembler/screenshots/basic-float-calls-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/basic-float-calls.png +0 -0
- package/docs/recipes/kickassembler/screenshots/basic-wedge-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/basic-wedge.png +0 -0
- package/docs/recipes/kickassembler/screenshots/big-font-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/big-font-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/charset-copy-rom-to-ram-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/charset-copy-rom-to-ram.png +0 -0
- package/docs/recipes/kickassembler/screenshots/cia-revision-detect-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/cia-revision-detect.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-cycling-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-cycling.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-fade-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/colour-fade.png +0 -0
- package/docs/recipes/kickassembler/screenshots/compare-16bit-signed-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/compare-16bit-signed.png +0 -0
- package/docs/recipes/kickassembler/screenshots/cracktro-template.png +0 -0
- package/docs/recipes/kickassembler/screenshots/crt-banked-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/crt-banked.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dos-error-codes-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dos-error-codes.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dot-flag-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dot-flag.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-job-queue-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-job-queue.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-via-probe-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/drive-via-probe.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dycp-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dycp-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dypp-sprite-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dypp-sprite-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dysp-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/dysp.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save-ntsc-run2.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save-run2.png +0 -0
- package/docs/recipes/kickassembler/screenshots/easyflash-save.png +0 -0
- package/docs/recipes/kickassembler/screenshots/eight-way-scroll-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/eight-way-scroll.png +0 -0
- package/docs/recipes/kickassembler/screenshots/file-io-roundtrip-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/file-io-roundtrip.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fire-effect-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fire-effect.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fld-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fld.png +0 -0
- package/docs/recipes/kickassembler/screenshots/fli-image.png +0 -0
- package/docs/recipes/kickassembler/screenshots/four-player-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/four-player-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/headless-verify-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/headless-verify.png +0 -0
- package/docs/recipes/kickassembler/screenshots/hello-world.png +0 -0
- package/docs/recipes/kickassembler/screenshots/high-score-insert-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/high-score-insert.png +0 -0
- package/docs/recipes/kickassembler/screenshots/hires-plot-line-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/hires-plot-line.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-chain-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-chain.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-owns-port-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/irq-owns-port.png +0 -0
- package/docs/recipes/kickassembler/screenshots/isometric-room-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/isometric-room.png +0 -0
- package/docs/recipes/kickassembler/screenshots/light-pen-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/light-pen-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/logic-rate-decoupling-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/logic-rate-decoupling.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace-average.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace-frame-b.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mci-interlace.png +0 -0
- package/docs/recipes/kickassembler/screenshots/memory-layout-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/memory-layout.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mouse-1351-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/mouse-1351-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/nmi-timer-tick-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/nmi-timer-tick.png +0 -0
- package/docs/recipes/kickassembler/screenshots/own-keyscan-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/own-keyscan.png +0 -0
- package/docs/recipes/kickassembler/screenshots/paddle-read-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/paddle-read.png +0 -0
- package/docs/recipes/kickassembler/screenshots/palette-cells-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/palette-cells.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pseudo-3d-road-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pseudo-3d-road.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pwm-digi-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/pwm-digi.png +0 -0
- package/docs/recipes/kickassembler/screenshots/raster-bars.png +0 -0
- package/docs/recipes/kickassembler/screenshots/reu-dma-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/reu-dma.png +0 -0
- package/docs/recipes/kickassembler/screenshots/runtime-relocation-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/runtime-relocation.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-dissolve-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-dissolve.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-wipe-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/screen-wipe.png +0 -0
- package/docs/recipes/kickassembler/screenshots/scroll-panel-split-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/scroll-panel-split.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sfx-in-player-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sfx-in-player.png +0 -0
- package/docs/recipes/kickassembler/screenshots/shadebobs-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/shadebobs.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sideborder-open.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sine-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sine-table-runtime-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sine-table-runtime.png +0 -0
- package/docs/recipes/kickassembler/screenshots/software-sprite-preshifted-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/software-sprite-preshifted.png +0 -0
- package/docs/recipes/kickassembler/screenshots/speedcode-generator-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/speedcode-generator.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-border-scroller-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-border-scroller.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-cache-flip-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-cache-flip.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-multiplex-24.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-multiplex-game-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-multiplex-game.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-priority-classes-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-priority-classes.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-sine-chain-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-sine-chain.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-stretcher-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprite-stretcher.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprites-only-screen-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sprites-only-screen.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sqrt-atan2-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/sqrt-atan2.png +0 -0
- package/docs/recipes/kickassembler/screenshots/stable-raster-irq-control.png +0 -0
- package/docs/recipes/kickassembler/screenshots/stable-raster-irq.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tech-tech-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tech-tech.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tod-alarm-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/tod-alarm.png +0 -0
- package/docs/recipes/kickassembler/screenshots/topbottom-border-open.png +0 -0
- package/docs/recipes/kickassembler/screenshots/twister-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/twister.png +0 -0
- package/docs/recipes/kickassembler/screenshots/unit-test-driver-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/unit-test-driver.png +0 -0
- package/docs/recipes/kickassembler/screenshots/vector-balls-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/vector-balls.png +0 -0
- package/docs/recipes/kickassembler/screenshots/wireframe-ships-ntsc.png +0 -0
- package/docs/recipes/kickassembler/screenshots/wireframe-ships.png +0 -0
- package/docs/recipes/kickassembler/scroll-panel-split.md +381 -0
- package/docs/recipes/kickassembler/sfx-in-player.md +907 -0
- package/docs/recipes/kickassembler/shadebobs.md +522 -0
- package/docs/recipes/kickassembler/sideborder-open.md +338 -0
- package/docs/recipes/kickassembler/sine-scroller.md +321 -0
- package/docs/recipes/kickassembler/sine-table-runtime.md +815 -0
- package/docs/recipes/kickassembler/software-sprite-preshifted.md +725 -0
- package/docs/recipes/kickassembler/speedcode-generator.md +606 -0
- package/docs/recipes/kickassembler/sprite-border-scroller.md +696 -0
- package/docs/recipes/kickassembler/sprite-cache-flip.md +1071 -0
- package/docs/recipes/kickassembler/sprite-multiplex-24.md +326 -0
- package/docs/recipes/kickassembler/sprite-multiplex-game.md +986 -0
- package/docs/recipes/kickassembler/sprite-priority-classes.md +463 -0
- package/docs/recipes/kickassembler/sprite-sine-chain.md +338 -0
- package/docs/recipes/kickassembler/sprite-stretcher.md +501 -0
- package/docs/recipes/kickassembler/sprites-only-screen.md +550 -0
- package/docs/recipes/kickassembler/sqrt-atan2.md +1014 -0
- package/docs/recipes/kickassembler/stable-raster-irq.md +360 -0
- package/docs/recipes/kickassembler/tape-turbo-loader.md +875 -0
- package/docs/recipes/kickassembler/tech-tech.md +679 -0
- package/docs/recipes/kickassembler/tod-alarm.md +754 -0
- package/docs/recipes/kickassembler/topbottom-border-open.md +407 -0
- package/docs/recipes/kickassembler/twister.md +518 -0
- package/docs/recipes/kickassembler/unit-test-driver.md +314 -0
- package/docs/recipes/kickassembler/vector-balls.md +545 -0
- package/docs/recipes/kickassembler/wireframe-ships.md +2324 -0
- package/docs/recipes/oscar64/actor-activation-window.md +807 -0
- package/docs/recipes/oscar64/adventure-engine.md +1094 -0
- package/docs/recipes/oscar64/attract-replay.md +465 -0
- package/docs/recipes/oscar64/beat-em-up-lanes.md +563 -0
- package/docs/recipes/oscar64/bfs-distance-map.md +723 -0
- package/docs/recipes/oscar64/bitmap-koala-viewer.md +355 -0
- package/docs/recipes/oscar64/car-contact.md +950 -0
- package/docs/recipes/oscar64/cave-scan.md +609 -0
- package/docs/recipes/oscar64/char-bullets.md +477 -0
- package/docs/recipes/oscar64/charset-animation.md +383 -0
- package/docs/recipes/oscar64/charset-parallax.md +401 -0
- package/docs/recipes/oscar64/destructible-terrain.md +1099 -0
- package/docs/recipes/oscar64/difficulty-tables.md +654 -0
- package/docs/recipes/oscar64/dig-and-guards.md +975 -0
- package/docs/recipes/oscar64/directory-reader.md +535 -0
- package/docs/recipes/oscar64/divide-check.md +581 -0
- package/docs/recipes/oscar64/double-buffer-nomirror.md +205 -0
- package/docs/recipes/oscar64/double-buffer.md +255 -0
- package/docs/recipes/oscar64/falling-blocks.md +874 -0
- package/docs/recipes/oscar64/fixed-point-jump-velocity.md +289 -0
- package/docs/recipes/oscar64/fixed-point-jump.md +210 -0
- package/docs/recipes/oscar64/flip-screen-rooms.md +793 -0
- package/docs/recipes/oscar64/frame-sync-loop-overrun.md +214 -0
- package/docs/recipes/oscar64/frame-sync-loop.md +254 -0
- package/docs/recipes/oscar64/game-tree-search.md +743 -0
- package/docs/recipes/oscar64/ghost-targeting.md +867 -0
- package/docs/recipes/oscar64/headless-verify.md +345 -0
- package/docs/recipes/oscar64/hello-world.md +83 -0
- package/docs/recipes/oscar64/high-score-persist.md +772 -0
- package/docs/recipes/oscar64/iffl-kernal-skip.md +421 -0
- package/docs/recipes/oscar64/joystick-input.md +358 -0
- package/docs/recipes/oscar64/lane-pursuit.md +1028 -0
- package/docs/recipes/oscar64/level-rle-decoder.md +620 -0
- package/docs/recipes/oscar64/lfsr-random-seed2.md +338 -0
- package/docs/recipes/oscar64/lfsr-random.md +403 -0
- package/docs/recipes/oscar64/load-asset-runtime.md +492 -0
- package/docs/recipes/oscar64/memory-layout.md +232 -0
- package/docs/recipes/oscar64/mixed-fighters.md +781 -0
- package/docs/recipes/oscar64/multi-sprite-object.md +533 -0
- package/docs/recipes/oscar64/nav-area-pathfinding.md +881 -0
- package/docs/recipes/oscar64/object-pool.md +572 -0
- package/docs/recipes/oscar64/pal-ntsc-detect.md +257 -0
- package/docs/recipes/oscar64/password-state.md +532 -0
- package/docs/recipes/oscar64/per-frame-hitbox.md +651 -0
- package/docs/recipes/oscar64/petscii-screen-codes.md +428 -0
- package/docs/recipes/oscar64/platformer-scaffold.md +1467 -0
- package/docs/recipes/oscar64/print-number.md +367 -0
- package/docs/recipes/oscar64/raster-bars.md +239 -0
- package/docs/recipes/oscar64/raster-profile-bars.md +404 -0
- package/docs/recipes/oscar64/rel-side-sectors.md +391 -0
- package/docs/recipes/oscar64/relative-file-records.md +378 -0
- package/docs/recipes/oscar64/save-load-seq-file.md +365 -0
- package/docs/recipes/oscar64/screenshots/README.md +99 -0
- package/docs/recipes/oscar64/screenshots/actor-activation-window-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/actor-activation-window.png +0 -0
- package/docs/recipes/oscar64/screenshots/adventure-engine-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/adventure-engine.png +0 -0
- package/docs/recipes/oscar64/screenshots/attract-replay-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/attract-replay.png +0 -0
- package/docs/recipes/oscar64/screenshots/beat-em-up-lanes-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/beat-em-up-lanes.png +0 -0
- package/docs/recipes/oscar64/screenshots/bfs-distance-map-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/bfs-distance-map.png +0 -0
- package/docs/recipes/oscar64/screenshots/bitmap-koala-viewer.png +0 -0
- package/docs/recipes/oscar64/screenshots/car-contact-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/car-contact.png +0 -0
- package/docs/recipes/oscar64/screenshots/cave-scan-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/cave-scan.png +0 -0
- package/docs/recipes/oscar64/screenshots/char-bullets-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/char-bullets.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-animation-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-animation.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-parallax-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/charset-parallax.png +0 -0
- package/docs/recipes/oscar64/screenshots/destructible-terrain-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/destructible-terrain.png +0 -0
- package/docs/recipes/oscar64/screenshots/difficulty-tables-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/difficulty-tables.png +0 -0
- package/docs/recipes/oscar64/screenshots/dig-and-guards-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/dig-and-guards.png +0 -0
- package/docs/recipes/oscar64/screenshots/directory-reader-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/directory-reader.png +0 -0
- package/docs/recipes/oscar64/screenshots/divide-check-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/divide-check.png +0 -0
- package/docs/recipes/oscar64/screenshots/double-buffer-nomirror.png +0 -0
- package/docs/recipes/oscar64/screenshots/double-buffer-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/double-buffer.png +0 -0
- package/docs/recipes/oscar64/screenshots/falling-blocks-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/falling-blocks.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump-velocity-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump-velocity.png +0 -0
- package/docs/recipes/oscar64/screenshots/fixed-point-jump.png +0 -0
- package/docs/recipes/oscar64/screenshots/flip-screen-rooms-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/flip-screen-rooms.png +0 -0
- package/docs/recipes/oscar64/screenshots/frame-sync-loop-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/frame-sync-loop-overrun.png +0 -0
- package/docs/recipes/oscar64/screenshots/frame-sync-loop.png +0 -0
- package/docs/recipes/oscar64/screenshots/game-tree-search-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/game-tree-search.png +0 -0
- package/docs/recipes/oscar64/screenshots/ghost-targeting-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/ghost-targeting.png +0 -0
- package/docs/recipes/oscar64/screenshots/headless-verify-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/headless-verify.png +0 -0
- package/docs/recipes/oscar64/screenshots/hello-world.png +0 -0
- package/docs/recipes/oscar64/screenshots/high-score-persist-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/high-score-persist.png +0 -0
- package/docs/recipes/oscar64/screenshots/iffl-kernal-skip-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/iffl-kernal-skip.png +0 -0
- package/docs/recipes/oscar64/screenshots/joystick-input-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/joystick-input.png +0 -0
- package/docs/recipes/oscar64/screenshots/lane-pursuit-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/lane-pursuit.png +0 -0
- package/docs/recipes/oscar64/screenshots/level-rle-decoder-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/level-rle-decoder.png +0 -0
- package/docs/recipes/oscar64/screenshots/lfsr-random-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/lfsr-random-seed2.png +0 -0
- package/docs/recipes/oscar64/screenshots/lfsr-random.png +0 -0
- package/docs/recipes/oscar64/screenshots/load-asset-runtime-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/load-asset-runtime.png +0 -0
- package/docs/recipes/oscar64/screenshots/memory-layout-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/memory-layout.png +0 -0
- package/docs/recipes/oscar64/screenshots/mixed-fighters-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/mixed-fighters.png +0 -0
- package/docs/recipes/oscar64/screenshots/multi-sprite-object-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/multi-sprite-object.png +0 -0
- package/docs/recipes/oscar64/screenshots/nav-area-pathfinding-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/nav-area-pathfinding.png +0 -0
- package/docs/recipes/oscar64/screenshots/object-pool-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/object-pool.png +0 -0
- package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-oldntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/pal-ntsc-detect-pal.png +0 -0
- package/docs/recipes/oscar64/screenshots/password-state-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/password-state.png +0 -0
- package/docs/recipes/oscar64/screenshots/per-frame-hitbox-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/per-frame-hitbox.png +0 -0
- package/docs/recipes/oscar64/screenshots/petscii-screen-codes-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/petscii-screen-codes.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold-d64-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold-d64.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/platformer-scaffold.png +0 -0
- package/docs/recipes/oscar64/screenshots/print-number-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/print-number.png +0 -0
- package/docs/recipes/oscar64/screenshots/raster-bars.png +0 -0
- package/docs/recipes/oscar64/screenshots/raster-profile-bars-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/raster-profile-bars.png +0 -0
- package/docs/recipes/oscar64/screenshots/rel-side-sectors-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/rel-side-sectors.png +0 -0
- package/docs/recipes/oscar64/screenshots/relative-file-records-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/relative-file-records.png +0 -0
- package/docs/recipes/oscar64/screenshots/save-load-seq-file-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/save-load-seq-file.png +0 -0
- package/docs/recipes/oscar64/screenshots/seeded-level-fill-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/seeded-level-fill.png +0 -0
- package/docs/recipes/oscar64/screenshots/sfx-engine-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/sfx-engine.png +0 -0
- package/docs/recipes/oscar64/screenshots/sid-music-player.png +0 -0
- package/docs/recipes/oscar64/screenshots/simple-shmup.png +0 -0
- package/docs/recipes/oscar64/screenshots/slope-collision-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/slope-collision.png +0 -0
- package/docs/recipes/oscar64/screenshots/soft-scroll-h.png +0 -0
- package/docs/recipes/oscar64/screenshots/sprite-animation-table-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/sprite-animation-table.png +0 -0
- package/docs/recipes/oscar64/screenshots/sprite-multiplex-8.png +0 -0
- package/docs/recipes/oscar64/screenshots/stable-raster-irq.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-input-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-input.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-gameover.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-notrail-control.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield-ungated-control.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-overlay-playfield.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-window-menu-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/text-window-menu.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-grid-collision-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-grid-collision.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-map-render-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/tile-map-render.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-player-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-player.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-word-parser-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/two-word-parser.png +0 -0
- package/docs/recipes/oscar64/screenshots/vehicle-control-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/vehicle-control.png +0 -0
- package/docs/recipes/oscar64/screenshots/wave-director-ntsc.png +0 -0
- package/docs/recipes/oscar64/screenshots/wave-director.png +0 -0
- package/docs/recipes/oscar64/seeded-level-fill.md +364 -0
- package/docs/recipes/oscar64/sfx-engine.md +514 -0
- package/docs/recipes/oscar64/sid-music-player.md +441 -0
- package/docs/recipes/oscar64/simple-shmup.md +1000 -0
- package/docs/recipes/oscar64/slope-collision.md +610 -0
- package/docs/recipes/oscar64/soft-scroll-h.md +261 -0
- package/docs/recipes/oscar64/sprite-animation-table.md +671 -0
- package/docs/recipes/oscar64/sprite-multiplex-8.md +314 -0
- package/docs/recipes/oscar64/stable-raster-irq.md +236 -0
- package/docs/recipes/oscar64/text-input.md +274 -0
- package/docs/recipes/oscar64/text-overlay-playfield.md +617 -0
- package/docs/recipes/oscar64/text-window-menu.md +551 -0
- package/docs/recipes/oscar64/tile-grid-collision.md +563 -0
- package/docs/recipes/oscar64/tile-map-render.md +395 -0
- package/docs/recipes/oscar64/two-player.md +528 -0
- package/docs/recipes/oscar64/two-word-parser.md +510 -0
- package/docs/recipes/oscar64/vehicle-control.md +771 -0
- package/docs/recipes/oscar64/wave-director.md +787 -0
- package/docs/recipes/runs.json +1606 -0
- package/docs/runtime/sim6502-reference.md +466 -0
- package/docs/runtime/vice-mcp-reference.md +351 -0
- package/docs/runtime/vice-reference.md +1393 -0
- package/docs/techniques/bitmap-modes.md +685 -0
- package/docs/techniques/cpu-cycle-tricks.md +1570 -0
- package/docs/techniques/effects-vector-3d.md +1431 -0
- package/docs/techniques/file-io.md +1427 -0
- package/docs/techniques/input.md +1072 -0
- package/docs/techniques/loaders-packers.md +768 -0
- package/docs/techniques/logic.md +3091 -0
- package/docs/techniques/maths.md +1524 -0
- package/docs/techniques/memory-banking.md +1686 -0
- package/docs/techniques/music-sid.md +1144 -0
- package/docs/techniques/raster.md +1543 -0
- package/docs/techniques/scroll.md +1292 -0
- package/docs/techniques/sprite.md +2566 -0
- package/docs/techniques/text-mode-render.md +717 -0
- package/docs/techniques/text.md +1095 -0
- package/docs/techniques/transitions.md +405 -0
- package/docs/toolchains/cartconv-reference.md +262 -0
- package/docs/toolchains/cc1541-reference.md +316 -0
- package/docs/toolchains/cc65-reference.md +667 -0
- package/docs/toolchains/error-sources/cc65/README.md +1 -0
- package/docs/toolchains/error-sources/cc65/missing-setcpu.s +4 -0
- package/docs/toolchains/error-sources/cc65/range-error.s +4 -0
- package/docs/toolchains/error-sources/cc65/segment-overflow.c +6 -0
- package/docs/toolchains/error-sources/cc65/unresolved-external.c +6 -0
- package/docs/toolchains/error-sources/kickassembler/README.md +1 -0
- package/docs/toolchains/error-sources/kickassembler/branch-out-of-range.asm +4 -0
- package/docs/toolchains/error-sources/kickassembler/failed-assert.asm +3 -0
- package/docs/toolchains/error-sources/kickassembler/flat-lib.asm +4 -0
- package/docs/toolchains/error-sources/kickassembler/import-source-no-namespace.asm +5 -0
- package/docs/toolchains/error-sources/kickassembler/macro-without-parentheses.asm +6 -0
- package/docs/toolchains/error-sources/kickassembler/memory-block-overlap.asm +4 -0
- package/docs/toolchains/error-sources/kickassembler/symbol-clash-lib.asm +5 -0
- package/docs/toolchains/error-sources/kickassembler/symbol-clash-main.asm +6 -0
- package/docs/toolchains/error-sources/kickassembler/unknown-symbol.asm +4 -0
- package/docs/toolchains/error-sources/oscar64/README.md +1 -0
- package/docs/toolchains/error-sources/oscar64/asm-addressing-mode.c +7 -0
- package/docs/toolchains/error-sources/oscar64/region-overflow.c +6 -0
- package/docs/toolchains/error-sources/oscar64/undefined-extern-var.c +5 -0
- package/docs/toolchains/error-sources/oscar64/undefined-extern.c +6 -0
- package/docs/toolchains/error-sources/oscar64/unknown-vic-field.c +6 -0
- package/docs/toolchains/error-sources/oscar64/void-main.c +3 -0
- package/docs/toolchains/kickassembler-reference.md +1020 -0
- package/docs/toolchains/memory-layout-planning.md +362 -0
- package/docs/toolchains/oscar64-headers-reference.md +583 -0
- package/docs/toolchains/oscar64-reference.md +892 -0
- package/docs/toolchains/petcat-reference.md +290 -0
- package/docs/toolchains/png2prg.md +188 -0
- package/docs/toolchains/release-disk.md +314 -0
- package/docs/toolchains/sidreloc.md +236 -0
- package/docs/toolchains/spindle-reference.md +487 -0
- package/docs/toolchains/tape-mastering.md +592 -0
- package/docs/toolchains/unit-testing-6502.md +281 -0
- package/docs/workflow/agent-harness.md +478 -0
- package/package.json +105 -0
- package/templates/_harness/CLAUDE.md.template +91 -0
- package/templates/_harness/PLAN.md.template +72 -0
- package/templates/_harness/check.py +442 -0
- package/templates/_harness/claude/settings.json +17 -0
- package/templates/_harness/gen-asm-header.py +96 -0
- package/templates/_harness/harness.mk +273 -0
- package/templates/_harness/hooks/plan-gate.py +185 -0
- package/templates/_harness/meter/frame_meter.asm +311 -0
- package/templates/_harness/meter/frame_meter.c +155 -0
- package/templates/_harness/meter/frame_meter.h +80 -0
- package/templates/_harness/zp-used.py +104 -0
- package/templates/action-puzzle/.claude/settings.json +17 -0
- package/templates/action-puzzle/.mcp.json +10 -0
- package/templates/action-puzzle/CLAUDE.md +120 -0
- package/templates/action-puzzle/Makefile +125 -0
- package/templates/action-puzzle/PLAN.md +388 -0
- package/templates/action-puzzle/README.md +240 -0
- package/templates/action-puzzle/expect.json +25 -0
- package/templates/action-puzzle/src/cave.c +280 -0
- package/templates/action-puzzle/src/cave.h +80 -0
- package/templates/action-puzzle/src/gen_autopilot.h +20 -0
- package/templates/action-puzzle/src/gen_caves.h +59 -0
- package/templates/action-puzzle/src/gen_notes.h +16 -0
- package/templates/action-puzzle/src/hiscore.c +152 -0
- package/templates/action-puzzle/src/hiscore.h +41 -0
- package/templates/action-puzzle/src/level.c +64 -0
- package/templates/action-puzzle/src/level.h +24 -0
- package/templates/action-puzzle/src/main.c +599 -0
- package/templates/action-puzzle/src/render.c +239 -0
- package/templates/action-puzzle/src/render.h +31 -0
- package/templates/action-puzzle/src/sound.c +158 -0
- package/templates/action-puzzle/src/sound.h +22 -0
- package/templates/action-puzzle/tools/disk_check.py +77 -0
- package/templates/action-puzzle/tools/drive.py +106 -0
- package/templates/action-puzzle/tools/gen.py +454 -0
- package/templates/action-puzzle/tools/model_check.py +49 -0
- package/templates/action-puzzle/tools/verdict_codes.py +24 -0
- package/templates/adventure/.claude/settings.json +17 -0
- package/templates/adventure/.mcp.json +10 -0
- package/templates/adventure/CLAUDE.md +118 -0
- package/templates/adventure/Makefile +113 -0
- package/templates/adventure/PLAN.md +396 -0
- package/templates/adventure/README.md +277 -0
- package/templates/adventure/expect.json +25 -0
- package/templates/adventure/src/engine.c +543 -0
- package/templates/adventure/src/engine.h +48 -0
- package/templates/adventure/src/gen_script.h +141 -0
- package/templates/adventure/src/gen_world.h +511 -0
- package/templates/adventure/src/main.c +439 -0
- package/templates/adventure/src/picture.c +83 -0
- package/templates/adventure/src/picture.h +16 -0
- package/templates/adventure/src/save.c +159 -0
- package/templates/adventure/src/save.h +25 -0
- package/templates/adventure/src/sound.c +71 -0
- package/templates/adventure/src/sound.h +16 -0
- package/templates/adventure/src/text.c +349 -0
- package/templates/adventure/src/text.h +40 -0
- package/templates/adventure/tools/disk_check.py +128 -0
- package/templates/adventure/tools/drive.py +119 -0
- package/templates/adventure/tools/gen.py +1033 -0
- package/templates/adventure/tools/world.py +356 -0
- package/templates/beat-em-up/.claude/settings.json +17 -0
- package/templates/beat-em-up/.mcp.json +10 -0
- package/templates/beat-em-up/CLAUDE.md +136 -0
- package/templates/beat-em-up/Makefile +96 -0
- package/templates/beat-em-up/PLAN.md +502 -0
- package/templates/beat-em-up/README.md +315 -0
- package/templates/beat-em-up/expect-gameover.json +8 -0
- package/templates/beat-em-up/expect.json +29 -0
- package/templates/beat-em-up/src/anim.c +70 -0
- package/templates/beat-em-up/src/art.c +344 -0
- package/templates/beat-em-up/src/autopilot.h +154 -0
- package/templates/beat-em-up/src/brute.c +273 -0
- package/templates/beat-em-up/src/enemy.c +281 -0
- package/templates/beat-em-up/src/engine.asm +512 -0
- package/templates/beat-em-up/src/fighter.c +384 -0
- package/templates/beat-em-up/src/game.h +269 -0
- package/templates/beat-em-up/src/hud.c +135 -0
- package/templates/beat-em-up/src/main.c +341 -0
- package/templates/beat-em-up/src/sound.c +44 -0
- package/templates/beat-em-up/src/street.c +93 -0
- package/templates/beat-em-up/src/verdict.h +523 -0
- package/templates/beat-em-up/src/view.c +362 -0
- package/templates/beat-em-up/tools/drive.py +105 -0
- package/templates/beat-em-up/tools/flickercheck.py +273 -0
- package/templates/demo/.claude/settings.json +17 -0
- package/templates/demo/.mcp.json +10 -0
- package/templates/demo/CLAUDE.md +129 -0
- package/templates/demo/Makefile +66 -0
- package/templates/demo/PLAN.md +401 -0
- package/templates/demo/README.md +322 -0
- package/templates/demo/expect.json +945 -0
- package/templates/demo/src/config.asm +80 -0
- package/templates/demo/src/framework.asm +390 -0
- package/templates/demo/src/main.asm +139 -0
- package/templates/demo/src/music.asm +197 -0
- package/templates/demo/src/part_main.asm +279 -0
- package/templates/demo/src/part_title.asm +79 -0
- package/templates/demo/src/parts.asm +43 -0
- package/templates/demo/src/tables.asm +73 -0
- package/templates/demo/src/verdict.asm +179 -0
- package/templates/demo/tools/audio.py +80 -0
- package/templates/demo/tools/gen_expect.py +167 -0
- package/templates/demo/tools/probe.py +95 -0
- package/templates/hello/.claude/settings.json +17 -0
- package/templates/hello/.mcp.json +10 -0
- package/templates/hello/CLAUDE.md +100 -0
- package/templates/hello/Makefile +38 -0
- package/templates/hello/PLAN.md +200 -0
- package/templates/hello/expect.json +20 -0
- package/templates/hello/src/main.c +161 -0
- package/templates/hello/src/sprite.asm +27 -0
- package/templates/hello-kick/.claude/settings.json +17 -0
- package/templates/hello-kick/.mcp.json +10 -0
- package/templates/hello-kick/CLAUDE.md +100 -0
- package/templates/hello-kick/Makefile +17 -0
- package/templates/hello-kick/PLAN.md +199 -0
- package/templates/hello-kick/expect.json +20 -0
- package/templates/hello-kick/src/main.asm +233 -0
- package/templates/platformer/.claude/settings.json +17 -0
- package/templates/platformer/.mcp.json +10 -0
- package/templates/platformer/CLAUDE.md +125 -0
- package/templates/platformer/Makefile +86 -0
- package/templates/platformer/PLAN.md +360 -0
- package/templates/platformer/README.md +277 -0
- package/templates/platformer/expect.json +32 -0
- package/templates/platformer/src/actors.c +289 -0
- package/templates/platformer/src/anim.c +51 -0
- package/templates/platformer/src/art.c +212 -0
- package/templates/platformer/src/autopilot.h +30 -0
- package/templates/platformer/src/engine.asm +253 -0
- package/templates/platformer/src/game.h +248 -0
- package/templates/platformer/src/hud.c +90 -0
- package/templates/platformer/src/level.c +184 -0
- package/templates/platformer/src/main.c +306 -0
- package/templates/platformer/src/player.c +268 -0
- package/templates/platformer/src/sound.c +42 -0
- package/templates/platformer/src/verdict.h +206 -0
- package/templates/platformer/src/view.c +331 -0
- package/templates/platformer/stage-expect.json +6 -0
- package/templates/platformer/tools/drive.py +112 -0
- package/templates/platformer/tools/tearcheck.py +229 -0
- package/templates/shmup-vertical/.claude/settings.json +17 -0
- package/templates/shmup-vertical/.mcp.json +10 -0
- package/templates/shmup-vertical/CLAUDE.md +137 -0
- package/templates/shmup-vertical/Makefile +131 -0
- package/templates/shmup-vertical/PLAN.md +569 -0
- package/templates/shmup-vertical/README.md +189 -0
- package/templates/shmup-vertical/expect.json +38 -0
- package/templates/shmup-vertical/src/bullets.c +158 -0
- package/templates/shmup-vertical/src/bullets.h +35 -0
- package/templates/shmup-vertical/src/display.c +239 -0
- package/templates/shmup-vertical/src/display.h +40 -0
- package/templates/shmup-vertical/src/game.h +85 -0
- package/templates/shmup-vertical/src/glyph.asm +358 -0
- package/templates/shmup-vertical/src/hiscore.c +141 -0
- package/templates/shmup-vertical/src/hiscore.h +16 -0
- package/templates/shmup-vertical/src/hit.asm +180 -0
- package/templates/shmup-vertical/src/hitbox.c +59 -0
- package/templates/shmup-vertical/src/hitbox.h +25 -0
- package/templates/shmup-vertical/src/kernel.asm +330 -0
- package/templates/shmup-vertical/src/level.c +174 -0
- package/templates/shmup-vertical/src/level.h +27 -0
- package/templates/shmup-vertical/src/main.c +824 -0
- package/templates/shmup-vertical/src/mux.asm +321 -0
- package/templates/shmup-vertical/src/sound.asm +255 -0
- package/templates/shmup-vertical/src/step.asm +169 -0
- package/templates/shmup-vertical/src/waves.c +195 -0
- package/templates/shmup-vertical/src/waves.h +33 -0
- package/templates/shmup-vertical/stage-expect.json +6 -0
- package/templates/shmup-vertical/tools/drive.py +152 -0
- package/templates/shmup-vertical/tools/joytest.py +159 -0
- package/templates/shmup-vertical/tools/meter.py +15 -0
- package/templates/shmup-vertical/tools/phases.py +73 -0
|
@@ -0,0 +1,1212 @@
|
|
|
1
|
+
<!-- doc-type: format-reference -->
|
|
2
|
+
|
|
3
|
+
# C64 File Formats
|
|
4
|
+
|
|
5
|
+
This document catalogs the file formats produced and consumed by the C64 toolchain and runtime ecosystem. Each section covers the physical layout, intended use, and relationships to tools in this knowledge base. Disk-image bit-level details follow the authoritative specifications published by Peter Schepers at `https://ist.uwaterloo.ca/~schepers/formats.html`. Producers and consumers reflect the current Tool node set; where the canonical producer is a music tracker or hardware-specific utility not yet represented as a Tool node, this is noted explicitly.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Executables
|
|
10
|
+
|
|
11
|
+
### .PRG — Program file (executable)
|
|
12
|
+
|
|
13
|
+
**Produced by:** oscar64, kickassembler, cc65
|
|
14
|
+
**Consumed by:** vice, c1541
|
|
15
|
+
|
|
16
|
+
The `.PRG` file is the fundamental C64 executable container. It consists of a 2-byte little-endian load address followed by raw 6502/6510 machine code. The load address tells the loader (KERNAL or a fastloader) where in the C64's 64 KiB address space to place the data; execution then begins either at the BASIC start vector or at an address specified via a `SYS` call or direct `RUN/STOP + RESTORE` sequence.
|
|
17
|
+
|
|
18
|
+
Oscar64 produces `.prg` directly via its built-in linker. KickAssembler emits `.prg` as its default output when a load address is specified in the source. cc65 requires a separate `ld65` linker invocation with an appropriate config file to produce a `.prg`.
|
|
19
|
+
|
|
20
|
+
The 2-byte header makes `.PRG` trivially distinct from `.BIN`: a load address of `$0801` is the standard BASIC program start for autostarting demos (the BASIC stub `10 SYS 2064` or similar). A load address of `$C000` or other high-memory address signals a directly-assembled utility or IRQ routine.
|
|
21
|
+
|
|
22
|
+
Maximum useful size is bounded by available RAM: roughly 38 KiB for a pure program (bank-switched or streaming loaders can exceed this). On real hardware, programs above approximately 50 KiB require multi-part loading.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### .CRT — Cartridge image
|
|
27
|
+
|
|
28
|
+
**Produced by:** oscar64, kickassembler, cartconv
|
|
29
|
+
**Consumed by:** vice, easyflash, ef3, cartconv
|
|
30
|
+
|
|
31
|
+
The `.CRT` format (defined by the VICE team, current spec v1.00) packages one or more ROM banks with metadata about the cartridge hardware type. It is the standard interchange format for C64 cartridge software and EasyFlash cart images used in the demoscene.
|
|
32
|
+
|
|
33
|
+
**File header (64 bytes):**
|
|
34
|
+
|
|
35
|
+
| Offset | Size | Field |
|
|
36
|
+
|--------|------|-------|
|
|
37
|
+
| $0000 | 16 | Magic string `"C64 CARTRIDGE "` (space-padded) |
|
|
38
|
+
| $0010 | 4 | Header length (big-endian; typically `$00000040`) |
|
|
39
|
+
| $0014 | 2 | Version (big-endian; `$0100` = v1.0) |
|
|
40
|
+
| $0016 | 2 | Hardware type (big-endian; 0 = generic 8K/16K) |
|
|
41
|
+
| $0018 | 1 | EXROM line state (0 = low/active) |
|
|
42
|
+
| $0019 | 1 | GAME line state (0 = low/active) |
|
|
43
|
+
| $001A | 1 | Hardware revision (subtype); 0 unless set. Measured with cartconv 3.10: `-s 1` wrote 1 here and set the version to `$0101`, and `cartconv -f` prints it as "Hardware Revision". An earlier version of this table folded it into six reserved bytes |
|
|
44
|
+
| $001B | 5 | Reserved (zero) |
|
|
45
|
+
| $0020 | 32 | Cartridge name (null-padded ASCII) |
|
|
46
|
+
|
|
47
|
+
**CHIP packets** follow the header, one per ROM bank:
|
|
48
|
+
|
|
49
|
+
| Offset | Size | Field |
|
|
50
|
+
|--------|------|-------|
|
|
51
|
+
| $0000 | 4 | Signature `"CHIP"` |
|
|
52
|
+
| $0004 | 4 | Total packet length including header (big-endian) |
|
|
53
|
+
| $0008 | 2 | Chip type: 0=ROM, 1=RAM (no ROM), 2=Flash ROM |
|
|
54
|
+
| $000A | 2 | Bank number (`$0000` for single-bank carts) |
|
|
55
|
+
| $000C | 2 | Load address (big-endian; `$8000` or `$A000` or `$E000`) |
|
|
56
|
+
| $000E | 2 | ROM image size in bytes (big-endian) |
|
|
57
|
+
| $0010 | var | ROM data |
|
|
58
|
+
|
|
59
|
+
The EXROM/GAME line combination determines the cartridge's memory mapping mode. Common combinations: EXROM=0/GAME=1 maps 8K at `$8000`; EXROM=0/GAME=0 maps 16K at `$8000`+`$A000`; EXROM=1/GAME=0 maps Ultimax (8K at `$E000`).
|
|
60
|
+
|
|
61
|
+
EasyFlash carts (hardware type `$0020`) contain up to 64 banks of 16K, each represented by two CHIP packets (one for `$8000`, one for `$A000`).
|
|
62
|
+
|
|
63
|
+
Oscar64 writes the container itself with `-tf=crt8`, `-tf=crt16` (type 0) or `-tf=crt` (EasyFlash). KickAssembler has no cartridge directive: in 5.25 `.crt` and `.bank` both fail with `Invalid directive` (run 2026-09-23), so a KickAssembler cartridge is either raw banks written with `outBin` and wrapped by cartconv, or a `.CRT` emitted byte by byte from the source as the `crt-banked` and `easyflash-save` recipes do. An earlier version of this paragraph named those two directives; they do not exist. The header and packet fields as decoded from files built by both tools, the type table, and what each type did when booted are in [cartconv-reference](../toolchains/cartconv-reference.md).
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### .BIN — Raw binary payload
|
|
68
|
+
|
|
69
|
+
**Produced by:** oscar64, kickassembler
|
|
70
|
+
**Consumed by:** vice, c1541
|
|
71
|
+
|
|
72
|
+
A raw binary file with no header — purely machine code or data at an implicit address. Used for ROM images, screen data, sprite sheets, or intermediate build artifacts before final linking. VICE can load `.BIN` files with an explicit address override via `-autostart-handle-tde` or the monitor's `l` command. The distinction from `.PRG` is the absence of the 2-byte load-address header.
|
|
73
|
+
|
|
74
|
+
KickAssembler emits `.bin` when the `.pc` directive is used without the auto-generated header. Oscar64 can emit raw binary payloads for specific linker segments.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Disk Images
|
|
79
|
+
|
|
80
|
+
Disk image formats capture the content of floppy disks used with the Commodore 1541, 1571, and 1581 drives. The canonical technical specifications are maintained by Peter Schepers; the descriptions below draw directly from those specifications.
|
|
81
|
+
|
|
82
|
+
### .D64 — Single-sided 35-track 1541 disk image
|
|
83
|
+
|
|
84
|
+
**Consumed by:** vice, c1541
|
|
85
|
+
|
|
86
|
+
The D64 is a sector-for-sector copy of a Commodore 1541 (or 1540) single-sided floppy disk. It is the most widely used C64 disk image format, standard for software distribution, fastloader testing, and demo release packaging.
|
|
87
|
+
|
|
88
|
+
**Physical layout:**
|
|
89
|
+
|
|
90
|
+
The disk has 35 tracks, numbered 1 (outermost) through 35 (innermost). Each track holds a varying number of 256-byte sectors depending on the track's position in the GCR speed zones:
|
|
91
|
+
|
|
92
|
+
| Tracks | Sectors per track | Cumulative sectors |
|
|
93
|
+
|--------|------------------|--------------------|
|
|
94
|
+
| 1–17 | 21 | 357 |
|
|
95
|
+
| 18–24 | 19 | 490 |
|
|
96
|
+
| 25–30 | 18 | 598 |
|
|
97
|
+
| 31–35 | 17 | 683 |
|
|
98
|
+
|
|
99
|
+
Total: **683 sectors, 174,848 bytes** (standard, no error bytes).
|
|
100
|
+
|
|
101
|
+
**Extended variants by file size:**
|
|
102
|
+
|
|
103
|
+
| Size (bytes) | Description |
|
|
104
|
+
|-------------|-------------|
|
|
105
|
+
| 174,848 | 35 tracks, no error bytes |
|
|
106
|
+
| 175,531 | 35 tracks + 683 error bytes appended |
|
|
107
|
+
| 196,608 | 40 tracks (SpeedDOS/Dolphin extension), no errors |
|
|
108
|
+
| 197,376 | 40 tracks + 768 error bytes |
|
|
109
|
+
|
|
110
|
+
Error bytes, when present, are appended after all sector data: one byte per sector, in track then sector order (track 1 sector 0 first, track 35 sector 16 last). **The byte is the 1541 floppy controller's job return code, not the DOS error number.** `$01` means the sector read cleanly; `$03` means "no sync", which the error channel reports as `21`. A tool that prints the byte as the DOS number is wrong for every non-zero value. The drive ROM's conversion from job code to DOS number is described in `iec-disk-reference.md`, "The 1541 DOS Error Codes"; the table below gives the codes an image can carry and what a stock 1541 prints for each.
|
|
111
|
+
|
|
112
|
+
| Byte | Controller condition | DOS number | VICE 3.10 (true drive, `U1` block read of a flagged sector, measured here) |
|
|
113
|
+
|------|----------------------|-----------|---------------------------------------------------------------------------|
|
|
114
|
+
| `$00` | none recorded; image tools treat it as no error | none | `0, OK` |
|
|
115
|
+
| `$01` | job returned OK | none | `0, OK` |
|
|
116
|
+
| `$02` | header not found | 20 | `20, READ ERROR` |
|
|
117
|
+
| `$03` | no sync | 21 | `20, READ ERROR` for one flagged sector; `21, READ ERROR` when every sector of the track carries `$03` |
|
|
118
|
+
| `$04` | data block not present | 22 | `22, READ ERROR` |
|
|
119
|
+
| `$05` | checksum error in the data block | 23 | `23, READ ERROR` |
|
|
120
|
+
| `$07` | verify error (write) | 25 | `0, OK`; a read cannot exercise it |
|
|
121
|
+
| `$08` | write protect on | 26 | `0, OK`; a read cannot exercise it |
|
|
122
|
+
| `$09` | checksum error in the header | 27 | `20, READ ERROR` |
|
|
123
|
+
| `$0B` | disk ID mismatch | 29 | `20, READ ERROR` for one flagged sector; `29, DISK ID MISMATCH` when every sector of the track carries `$0B` |
|
|
124
|
+
| `$0F` | drive not ready | 74 | `0, OK` |
|
|
125
|
+
|
|
126
|
+
The measurement column comes from one run of the windowless x64sc build of VICE 3.10 with `-drive8truedrive -drive8type 1541` and a 175,531-byte image whose flagged sectors sat one per track; three PAL runs produced byte-identical screens. VICE 3.10 builds each sector's GCR from the byte (its `gcr.c`): `$03` replaces the sync marks, `$02` the header block ID, `$09` the header checksum, `$0B` the header's disk ID, `$04` the data block ID and `$05` the data checksum. It does nothing for `$07`, `$08` and `$0F`, so a read of such a sector succeeds. The two "for one flagged sector" rows come from the drive ROM running under emulation, not from the injection: VICE alters exactly the bytes named and the ROM reports what it reports. The reading is consistent with the ROM matching a wanted header by comparing the raw GCR it reads against an image of the header it expects, so a header with a wrong checksum or ID is never matched and the search times out as `20`; that account of the ROM is not measured here. Codes `$0A` (28) and `$10` (24) were not measured here. Whether a write (`U2`) to a `$08` sector reports `26` was not measured here.
|
|
127
|
+
|
|
128
|
+
**Directory and BAM (track 18):**
|
|
129
|
+
|
|
130
|
+
Track 18 is the directory track. Sector 0 holds the Block Availability Map (BAM) and disk metadata. Sectors 1–18 hold directory entries (maximum 144 entries; 8 entries × 18 sectors with 3-sector interleave: 18/1 → 18/4 → 18/7 ...).
|
|
131
|
+
|
|
132
|
+
BAM structure at 18/0:
|
|
133
|
+
|
|
134
|
+
| Offset | Size | Field |
|
|
135
|
+
|--------|------|-------|
|
|
136
|
+
| $00–$01 | 2 | Pointer to first directory sector (18, 1) |
|
|
137
|
+
| $02 | 1 | DOS version (`$41` = CBM DOS 2; other = soft write-protect) |
|
|
138
|
+
| $03 | 1 | Unused |
|
|
139
|
+
| $04–$8F | 140 | 35 × 4-byte BAM entries (one per track) |
|
|
140
|
+
| $90–$9F | 16 | Disk name (PETASCII, `$A0`-padded) |
|
|
141
|
+
| $A0–$A1 | 2 | Filler `$A0` |
|
|
142
|
+
| $A2–$A3 | 2 | Disk ID (2-char PETASCII) |
|
|
143
|
+
| $A4 | 1 | Filler `$A0` |
|
|
144
|
+
| $A5–$A6 | 2 | DOS type (`"2A"`) |
|
|
145
|
+
|
|
146
|
+
Each 4-byte BAM entry: first byte = free sector count, next 3 bytes = 24-bit bitmap (bit 1 = free, bit 0 = allocated), covering the sectors of that track left-to-right.
|
|
147
|
+
|
|
148
|
+
**Directory entry (32 bytes each):**
|
|
149
|
+
|
|
150
|
+
| Offset | Size | Field |
|
|
151
|
+
|--------|------|-------|
|
|
152
|
+
| $00–$01 | 2 | Track/sector pointer to next directory sector (0/0 if last) |
|
|
153
|
+
| $02 | 1 | File type (`$82`=PRG, `$81`=SEQ, `$83`=USR, `$84`=REL) |
|
|
154
|
+
| $03–$04 | 2 | First sector of file (track, sector) |
|
|
155
|
+
| $05–$14 | 16 | Filename (PETASCII, `$A0`-padded) |
|
|
156
|
+
| $15–$16 | 2 | REL only: track and sector of the first side sector (measured below; `$00 $00` on other types, not measured here) |
|
|
157
|
+
| $17 | 1 | REL only: record length (the DOS's stated range is 1 to 254; 32, 100 and 254 measured below) |
|
|
158
|
+
| $1E–$1F | 2 | File size in sectors (little-endian); for a REL file the side sectors are counted in |
|
|
159
|
+
|
|
160
|
+
Directory art lives entirely in these bytes: a type of `$80` (DEL, closed), a first-sector pointer of `0 0` for an entry that owns no blocks, any PETSCII in the name field and any value at `$1E`, all written from the host by [cc1541](../toolchains/cc1541-reference.md), which also shows what the drive's listing makes of an `$A0` inside a name.
|
|
161
|
+
|
|
162
|
+
File data uses a 10-sector interleave chain (each sector's first two bytes are the track/sector link to the next; the remaining 254 bytes are data). The last sector in a chain uses `$00` as the next-track link and stores the index of the last used byte in what would normally be the next-sector byte, so the sector holds that value minus one data bytes (an earlier version said it stored the count of data bytes; a 91-byte last sector written by the 1541 in VICE holds 92).
|
|
163
|
+
|
|
164
|
+
**REL file (type `$84`):**
|
|
165
|
+
|
|
166
|
+
A relative file is a data chain like any other plus one or more **side sectors**, blocks that list the track and sector of every data block in order so the drive can turn a record number into a block without walking the chain. Everything in this subsection was read off `.d64` images written by the 1541 ROM under VICE x64sc 3.10 (`-drive8truedrive`, windowless build) by the recipe `../recipes/oscar64/rel-side-sectors.md` and a side run of the same shape with 254-byte records; the recipe also prints the same bytes from the C64 side through `U1`. The images were decoded on the host with this script, which walks the directory, the side-sector chain and the data chain (`SPT` is the sectors-per-track table above):
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
img = open('disk.d64', 'rb').read()
|
|
170
|
+
SPT = [21]*17 + [19]*7 + [18]*6 + [17]*5
|
|
171
|
+
def blk(t, s):
|
|
172
|
+
o = (sum(SPT[:t-1]) + s) * 256
|
|
173
|
+
return img[o:o+256]
|
|
174
|
+
ent = blk(18, 1)[0:32] # first directory entry
|
|
175
|
+
print(hex(ent[2]), ent[3], ent[4], ent[0x15], ent[0x16], ent[0x17],
|
|
176
|
+
ent[0x1e] | ent[0x1f] << 8)
|
|
177
|
+
t, s = ent[0x15], ent[0x16]
|
|
178
|
+
while t: # side-sector chain
|
|
179
|
+
b = blk(t, s)
|
|
180
|
+
print(t, s, 'next', b[0], b[1], 'number', b[2], 'reclen', b[3],
|
|
181
|
+
'group', [(b[4+2*i], b[5+2*i]) for i in range(6)],
|
|
182
|
+
'data', [(b[16+2*i], b[17+2*i]) for i in range(120) if b[16+2*i]])
|
|
183
|
+
t, s = b[0], b[1]
|
|
184
|
+
t, s = ent[3], ent[4] # data chain
|
|
185
|
+
while t:
|
|
186
|
+
b = blk(t, s)
|
|
187
|
+
print(t, s, 'link', b[0], b[1])
|
|
188
|
+
t, s = b[0], b[1]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The directory entry of the recipe's file `SS`, created with `SS,L,` and the byte 100, eight records written, then closed:
|
|
192
|
+
|
|
193
|
+
| Offset | Bytes read | Meaning |
|
|
194
|
+
|--------|-----------|---------|
|
|
195
|
+
| $02 | `$84` | REL, closed. The recipe read the entry through `U1` while the file was still open and the byte was already `$84`. |
|
|
196
|
+
| $03–$04 | `$11 $00` | first data block, 17/0 |
|
|
197
|
+
| $15–$16 | `$11 $0A` | first side sector, 17/10 |
|
|
198
|
+
| $17 | `$64` | record length 100 |
|
|
199
|
+
| $1E–$1F | `$05 $00` | 5 blocks: 4 data blocks and 1 side sector. Read while the file was open it was `$00 $00`; the drive writes the count at close. |
|
|
200
|
+
|
|
201
|
+
The side sector at 17/10, byte by byte:
|
|
202
|
+
|
|
203
|
+
| Offset | Size | Bytes read | Field |
|
|
204
|
+
|--------|------|-----------|-------|
|
|
205
|
+
| $00–$01 | 2 | `$00 $17` | Track and sector of the next side sector. Track 0 marks the last one, and the sector byte is then the index of the last used byte in this block: `$17` = 23 is the last byte of the fourth data-block pair (16 + 4 × 2 − 1). |
|
|
206
|
+
| $02 | 1 | `$00` | This side sector's number in the group, 0 to 5. |
|
|
207
|
+
| $03 | 1 | `$64` | Record length, the same value as the directory entry's `$17`. |
|
|
208
|
+
| $04–$0F | 12 | `$11 $0A` then ten `$00` | Track and sector of side sectors 0 to 5 of the group, in order, `$00 $00` for those that do not exist. Every side sector carries the whole list. |
|
|
209
|
+
| $10–$FF | 240 | `$11 $00 $11 $0B $11 $01 $11 $0C` then zeros | Track and sector of data blocks, in file order, up to 120 pairs; `$00` in a track position ends the list. |
|
|
210
|
+
|
|
211
|
+
So the file's data chain is 17/0, 17/11, 17/1, 17/12, and the chain's own links agree with the list: each block's bytes 0 and 1 name the next, and the last block's link is `$00 $EF`. That `$EF` = 239 is the last used byte of the data chain, and it is not where record 8 ends: 4 blocks hold 1,016 data bytes, ten 100-byte records fit, and the drive fills every allocated block with whole records, so records 9 and 10 exist on the disk though nobody wrote them, and 239 = 2 + 1000 − 762 − 1 is the last byte of record 10 (both records read back from the image as `$FF` followed by 99 `$00`; a `P` to record 10 answered `00` and read one `$FF`, a `P` to record 11 answered `50`). The last 16 data bytes of the block, after record 10, hold `$FF` then fifteen `$00`: the drive marks the start of every record slot it allocates, including the slot record 11 would begin in, even though record 11 does not fit and a `P` to it answers `50` (read from the image: `$FF` sits at block bytes 40, 140 and 240 of 17/12, one per 100-byte slot).
|
|
212
|
+
|
|
213
|
+
**Record contents and padding.** Every record the drive allocates starts as `$FF` followed by zeros. A record written short is padded with `$00` to the record length: record 3 of the recipe was written as ten bytes and reads back from the image as those ten bytes then ninety `$00`, and a read of it through the drive returns ten bytes with EOF, the padding not sent. A record can straddle two blocks: with 100-byte records, record 3 occupies bytes 200 to 253 of the first block and 0 to 45 of the second, and the program never sees the seam.
|
|
214
|
+
|
|
215
|
+
**Two side sectors.** The side run created `BIG,L,` with the byte 254, sent one `P` to record 125 (reply `50, RECORD NOT PRESENT`) and wrote one byte to it (reply `00, OK`). That single write allocated 125 data blocks and two side sectors; the entry then read `$84`, side sector 17/10, record length `$FE`, 127 blocks. Side sector 0 at 17/10 held next = 12/9, number 0, and 120 data-block pairs; side sector 1 at 12/9 held next = `$00 $19` (25 = 16 + 5 × 2 − 1), number 1, and 5 pairs; both carried the group list `$11 $09 $0C $09` then zeros, that is 17/10 and 12/9. The 125-block data chain matched the two lists end to end, and the last block's link was `$00 $FF` because a 254-byte record fills a block exactly. After that write a `P` to record 125 answered `00` and a `P` to record 126 answered `50`: with 254-byte records no extra records fall out of the allocation.
|
|
216
|
+
|
|
217
|
+
**Limits.** The record length byte is 1 to 254; the DOS's stated range, and the recipes use 32, 100 and 254 (the length 0 and 255 cases were not measured here). A group holds six side sectors of 120 data blocks each, 720 blocks (arithmetic from the tables above), which is more than a 35-track disk has free, so on a 1541 the disk is the limit, and the DOS's stated reply for a `P` past it is `52, FILE TOO LARGE` (not provoked here; its ROM site is in `iec-disk-reference.md`, "The 1541 DOS Error Codes"). The record number in the `P` command is two bytes, so 65,535 is the highest a program can name (arithmetic; not measured here). Drives with more capacity extend the scheme with a further block that lists groups; that is not measured here and this table is the 1541's.
|
|
218
|
+
|
|
219
|
+
**The P command's byte order.** Five bytes on the command channel: `P`, the data channel's secondary address plus 96 (`$62` for channel 2), the record number low byte, the record number high byte, and the byte within the record counted from 1. Measured in the recipe and its second run: `P` to record 5 byte 1 read all 100 bytes of record 5; `P` to record 5 byte 7 read 94 bytes starting at the seventh; a `P` to a record that does not fit in the allocated data blocks answers `50` and a read after it returns one `$0D` with the status still `50`; a write after that `50` extends the file and answers `00`. Which records answer `50` depends on the record length and on what is allocated: a record answers `00` when the whole of it fits in the allocated data blocks and `50` when any of it would lie past them. With 100-byte records and one allocated block (254 data bytes), records 1 and 2 answered `00` and record 3, whose bytes 200 to 299 cross the end, answered `50`; after the write to 3 allocated the second block (508 bytes), 4 and 5 answered `00` and 6 (500 to 599) answered `50`; after the third block (762 bytes), 7 answered `00` and 8 (700 to 799) answered `50`. The same rule gives the seven records of 32 bytes that fit one block in `../techniques/file-io.md` (`kernal_relative_file_io`) and the ten of 100 bytes that fit four.
|
|
220
|
+
|
|
221
|
+
**Typical use:** release distribution, fastloader authoring (Krill/Loader, Spindle, DreamLoad), scene release packaging via c1541 or CBM FileBrowser.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### .D71 — Double-sided 1571 disk image
|
|
226
|
+
|
|
227
|
+
**Consumed by:** vice, c1541
|
|
228
|
+
|
|
229
|
+
The D71 represents a Commodore 1571 double-sided disk. It is two D64-equivalent sides concatenated, with modified BAM bookkeeping to track both sides.
|
|
230
|
+
|
|
231
|
+
**Physical layout:**
|
|
232
|
+
|
|
233
|
+
Side 0 occupies tracks 1–35 (identical sector counts to D64). Side 1 occupies tracks 36–70 (mirroring side 0: tracks 36–52 have 21 sectors, 53–59 have 19, 60–65 have 18, 66–70 have 17).
|
|
234
|
+
|
|
235
|
+
Total: **1,366 sectors, 349,696 bytes** (without error bytes); 351,062 bytes with 1,366 error bytes appended.
|
|
236
|
+
|
|
237
|
+
**BAM structure:**
|
|
238
|
+
|
|
239
|
+
The primary BAM remains at track 18/0 and covers side 0 (tracks 1–35) as in D64. The side 1 BAM is stored at track 53/0: bytes `$DD–$FF` in the primary BAM sector hold free-sector counts for tracks 36–70.
|
|
240
|
+
|
|
241
|
+
Directory: track 18, same 144-file maximum. Native 1571 mode uses 6-sector interleave (versus 10 for 1541-compatibility mode).
|
|
242
|
+
|
|
243
|
+
**Typical use:** larger software titles, tools requiring more than 664 KiB of disk storage.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### .D81 — 1581 disk image
|
|
248
|
+
|
|
249
|
+
**Consumed by:** vice, c1541
|
|
250
|
+
|
|
251
|
+
The D81 represents a Commodore 1581 double-density 3.5-inch disk, offering substantially more capacity than either the 1541 or 1571.
|
|
252
|
+
|
|
253
|
+
**Physical layout:**
|
|
254
|
+
|
|
255
|
+
80 tracks, each with 40 sectors of 256 bytes each. Total: **3,200 sectors, 819,200 bytes** (822,400 bytes with 3,200 error bytes appended).
|
|
256
|
+
|
|
257
|
+
**BAM and directory structure:**
|
|
258
|
+
|
|
259
|
+
The header, BAM, and first directory entries all reside on track 40:
|
|
260
|
+
|
|
261
|
+
| Track/Sector | Contents |
|
|
262
|
+
|-------------|----------|
|
|
263
|
+
| 40/0 | Disk header (name, ID, DOS version) |
|
|
264
|
+
| 40/1 | BAM for tracks 1–40 |
|
|
265
|
+
| 40/2 | BAM for tracks 41–80 |
|
|
266
|
+
| 40/3 | First directory sector |
|
|
267
|
+
|
|
268
|
+
BAM entries use 6 bytes each: 1 byte free-sector count + 5 bytes (40-bit bitmap).
|
|
269
|
+
|
|
270
|
+
**Key differences from D64/D71:** sector interleave is 1 for both files and directories (the 1581 buffers a full track in internal RAM, making interleave irrelevant for sequential read performance). Maximum of approximately 296 directory entries at the root. Supports subdirectories and partitions at the DOS level (not represented in the D81 image format itself).
|
|
271
|
+
|
|
272
|
+
**Typical use:** large software archives, tools requiring subdirectory support.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### .G64 — GCR-encoded 1541 disk image (with full track layout)
|
|
277
|
+
|
|
278
|
+
**Consumed by:** vice, c1541
|
|
279
|
+
|
|
280
|
+
The G64 format preserves the raw GCR (Group Code Recording) encoding of a 1541 disk, including inter-sector gaps, SYNC marks, and variable track lengths. Unlike D64, which discards the physical encoding and retains only sector data, G64 retains the full magnetic track layout. This makes G64 essential for copy-protected software, custom loader timing, and anything where the exact physical track structure matters.
|
|
281
|
+
|
|
282
|
+
**File header (12 bytes):**
|
|
283
|
+
|
|
284
|
+
| Offset | Size | Field |
|
|
285
|
+
|--------|------|-------|
|
|
286
|
+
| $0000–$0007 | 8 | Signature `"GCR-1541"` |
|
|
287
|
+
| $0008 | 1 | Version (`$00`) |
|
|
288
|
+
| $0009 | 1 | Track count (typically `$54` = 84, for 42 tracks × half-tracks) |
|
|
289
|
+
| $000A–$000B | 2 | Maximum track size in bytes, little-endian (commonly 7,928) |
|
|
290
|
+
|
|
291
|
+
**Track offset table** (starting at `$000C`): 84 × 4-byte little-endian absolute file offsets, one per track/half-track position. A zero entry means no data is stored for that position. Standard 35-track images have non-zero entries only for tracks 1–35 (positions 0, 2, 4, ... 68 in the table); half-track entries are zero.
|
|
292
|
+
|
|
293
|
+
**Speed zone table** (follows the offset table): 84 × 4-byte entries. Values 0–3 are direct speed zone assignments for the entire track. Values ≥ 4 are file offsets pointing to per-byte speed zone blocks (1,982 bytes each), encoding speed for each 4-byte group in the track.
|
|
294
|
+
|
|
295
|
+
**Track data** (at each non-zero offset): 2-byte little-endian actual track length, followed by the raw GCR bytes (padded with `$FF` to the maximum track size).
|
|
296
|
+
|
|
297
|
+
**GCR sector structure within a track:**
|
|
298
|
+
|
|
299
|
+
Each sector occupies a fixed pattern:
|
|
300
|
+
1. Header SYNC: 5 × `$FF` bytes (40 consecutive 1-bits)
|
|
301
|
+
2. Header block: 10 GCR-encoded bytes (block ID `$08`, checksum, sector, track, format IDs)
|
|
302
|
+
3. Header gap: 8–9 bytes of `$55`
|
|
303
|
+
4. Data SYNC: 5 × `$FF` bytes
|
|
304
|
+
5. Data block: 325 GCR-encoded bytes (256 data bytes + block ID `$07` + checksum + padding, GCR-encoded at 4 bytes → 5 bytes ratio)
|
|
305
|
+
6. Inter-sector gap: 4–19 bytes (variable; `$55`)
|
|
306
|
+
|
|
307
|
+
**Correction (2026-09-23).** Item 5 used to be the whole of what this page said about the code: "GCR-encoded at 4 bytes → 5 bytes ratio". The ratio is right and it is not how the code works. The 1541 splits every byte into two nibbles and replaces each nibble with a five-bit word from a sixteen-entry table, so four bytes fill five, and a decoder written from the ratio alone cannot read a sector. Items 3 and 6 gave the gaps as ranges; in the image measured below c1541 writes 9 and 8. The subsection "GCR encoding" gives the table, the two block layouts, the sync and gaps, and the zones, each as decoded from a G64 or read out of the drive ROM.
|
|
308
|
+
|
|
309
|
+
Speed zones (1541-standard):
|
|
310
|
+
|
|
311
|
+
| Track range | Zone | Nominal track size |
|
|
312
|
+
|------------|------|-------------------|
|
|
313
|
+
| 1–17 | 3 | 7,820 bytes |
|
|
314
|
+
| 18–24 | 2 | 7,170 bytes |
|
|
315
|
+
| 25–30 | 1 | 6,300 bytes |
|
|
316
|
+
| 31–35 | 0 | 6,020 bytes |
|
|
317
|
+
|
|
318
|
+
#### GCR encoding
|
|
319
|
+
|
|
320
|
+
Every figure in this subsection was measured here unless it says otherwise. A G64 was formatted and written on the host with VICE 3.10's `c1541` (`c1541 -format "TEST,01" g64 disk.g64 -write known.prg known`, where `known.prg` is 263 bytes: load address `$0801`, the bytes `$00` to `$FF` in order, then `KNOWN`). The image was decoded with the Python script at the end of the subsection, and it was read back under the windowless x64sc build of VICE 3.10 with true drive emulation, where `LOAD"KNOWN",8,1` printed `LOADING` and then `READY.`. The ROM figures come from the 1541 ROM image VICE ships, `DRIVES/dos1541-325302-01+901229-05.bin`, 16,384 bytes mapped at `$C000`.
|
|
321
|
+
|
|
322
|
+
**Speed zones.** The image's track table and speed-zone table held, for the 35 tracks with data (every half-track offset was zero):
|
|
323
|
+
|
|
324
|
+
| Tracks | Zone entry | Track length (bytes) | Sectors | Bit rate at 300 rpm | Bit cell |
|
|
325
|
+
|--------|------------|----------------------|---------|---------------------|----------|
|
|
326
|
+
| 1–17 | 3 | 7,692 | 21 | 307,680 bit/s | 3.25 µs |
|
|
327
|
+
| 18–24 | 2 | 7,142 | 19 | 285,680 bit/s | 3.50 µs |
|
|
328
|
+
| 25–30 | 1 | 6,666 | 18 | 266,640 bit/s | 3.75 µs |
|
|
329
|
+
| 31–35 | 0 | 6,250 | 17 | 250,000 bit/s | 4.00 µs |
|
|
330
|
+
|
|
331
|
+
The bit-rate and bit-cell columns are arithmetic on the track length: bytes × 8 × 5 revolutions a second. The four rates are 16 MHz divided by 52, 56, 60 and 64, which is the drive's 16 MHz crystal divided by 13, 14, 15 or 16 and then by four per bit cell; the crystal and the divide-by-four are not measured here. The sector counts and the zone boundaries are in the ROM: `$FED1` holds `11 12 13 15` (17, 18, 19 and 21 sectors, zone 0 first) and `$FED7` holds `24 1F 19 12` (36, 31, 25 and 18, the first track above each zone, zone 0 first). The zone numbers in the G64 table are the ROM's: zone 3 is the outermost, fastest zone. The "nominal track size" column in the table above this subsection is not what c1541 3.10 writes; the lengths it wrote are the ones here.
|
|
332
|
+
|
|
333
|
+
**The code table.** Each byte is split into two nibbles and each nibble becomes a five-bit word, high nibble first, so four bytes occupy five bytes on the track. The table was derived from the image alone, from the bytes whose values were known before decoding (the `$08` and `$07` block IDs, the `$0F $0F` header padding, the track number, the 254 file bytes in the first data block and its `$00 $00` padding), and every one of the sixteen entries then matched the ROM's encode table at `$F77F`, which reads `0A 0B 12 13 0E 0F 16 17 09 19 1A 1B 0D 1D 1E 15` and is the only run of sixteen distinct five-bit values with no three consecutive zero bits in the ROM:
|
|
334
|
+
|
|
335
|
+
| Nibble | Code | Nibble | Code | Nibble | Code | Nibble | Code |
|
|
336
|
+
|--------|------|--------|------|--------|------|--------|------|
|
|
337
|
+
| `0` | `01010` | `4` | `01110` | `8` | `01001` | `C` | `01101` |
|
|
338
|
+
| `1` | `01011` | `5` | `01111` | `9` | `11001` | `D` | `11101` |
|
|
339
|
+
| `2` | `10010` | `6` | `10110` | `A` | `11010` | `E` | `11110` |
|
|
340
|
+
| `3` | `10011` | `7` | `10111` | `B` | `11011` | `F` | `10101` |
|
|
341
|
+
|
|
342
|
+
By inspection of the sixteen words: none holds three zero bits in a row, none starts with more than one zero and none ends with more than one, so two words side by side never put more than two zeros together, which is what the drive's read clock needs; and no word starts or ends with more than four ones, so the longest run of ones inside data is eight, short of the ten that make a sync. `F` is `10101`, not `11111`, for the second reason.
|
|
343
|
+
|
|
344
|
+
**Header block.** After each header sync, 10 GCR bytes decode to 8:
|
|
345
|
+
|
|
346
|
+
| Offset | Size | Field | Decoded, track 17 sector 0 |
|
|
347
|
+
|--------|------|-------|----------------------------|
|
|
348
|
+
| 0 | 1 | Header block ID | `$08` |
|
|
349
|
+
| 1 | 1 | Checksum, the XOR of bytes 2 to 5 | `$11` |
|
|
350
|
+
| 2 | 1 | Sector | `$00` |
|
|
351
|
+
| 3 | 1 | Track | `$11` (17) |
|
|
352
|
+
| 4 | 1 | Disk ID, second character | `$A0` |
|
|
353
|
+
| 5 | 1 | Disk ID, first character | `$A0` |
|
|
354
|
+
| 6–7 | 2 | Padding | `$0F $0F` |
|
|
355
|
+
|
|
356
|
+
The 10 GCR bytes were `52 56 B5 29 6B D2 B4 A5 55 55`. All 21 headers on track 17 carried sectors 0 to 20 in order, each with a checksum equal to the XOR of its sector, track and two ID bytes. The disk ID is worth a look: c1541 3.10 wrote `$A0 $A0` into every sector header, while the BAM at track 18 sector 0 (bytes `$A2`–`$A3`) holds `30 31`, the `01` given on the command line, and the directory listing shows `01`. The disk loaded all the same under true drive emulation, as above; the ROM is documented as taking the ID from a sector header when it initialises a disk rather than from the BAM, which would explain that, but the mechanism is not measured here. A tool that expects the header ID to match the directory line will not find that in a c1541-formatted G64. Because both ID bytes were `$A0`, which of the two characters comes first on the track was not measured here; the order in the table is the ROM's as documented, not confirmed by this image. What the ROM's own formatter writes into the header on a real disk is not measured here either.
|
|
357
|
+
|
|
358
|
+
**Data block.** After each data sync, 325 GCR bytes decode to 260:
|
|
359
|
+
|
|
360
|
+
| Offset | Size | Field | Decoded, track 17 sector 0 |
|
|
361
|
+
|--------|------|-------|----------------------------|
|
|
362
|
+
| 0 | 1 | Data block ID | `$07` |
|
|
363
|
+
| 1–256 | 256 | Sector data | `11 0A` (link: track 17, sector 10), then `01 08 00 01 02` ... `FB` |
|
|
364
|
+
| 257 | 1 | Checksum, the XOR of the 256 data bytes | `$12` |
|
|
365
|
+
| 258–259 | 2 | Padding | `$00 $00` |
|
|
366
|
+
|
|
367
|
+
The 256 data bytes were the file's first sector as DOS lays it out: the two-byte link to the next sector, then 254 file bytes (the load address `01 08`, then `$00` to `$FB`), and the XOR of those 256 bytes is `$12`, as stored.
|
|
368
|
+
|
|
369
|
+
**Sync and gaps.** As c1541 3.10 wrote this image, byte aligned: 5 × `$FF` (40 one bits), the 10 header bytes, 9 × `$55`, 5 × `$FF`, the 325 data bytes, 8 × `$55`, then the next sector's sync. That is 362 bytes a sector, 21 × 362 = 7,602, and the rest of the 7,692-byte track is `$55` (98 bytes in a row after the last data block, the 8-byte gap included). A scan of the bit stream for runs of ten or more one bits finds 42 on the track, one before each header and one before each data block; the drive's detector fires on ten, so a 40-bit sync is four times what it needs and `disk_protection_tricks` in `../techniques/loaders-packers.md` is about what a loader does with that slack and with the fields above. The gaps a real 1541's formatter writes depend on the track's spare space and are not measured here. The bit clock per zone is why `gcr_timing_assumes_stock_drive` in `../pitfalls/loader.md` exists: a drive that is not stepping its clock through those four rates reads the same bits at the wrong cell width.
|
|
370
|
+
|
|
371
|
+
**Decoder.** The script that produced the figures above. It takes the image, a track, a sector, and the file whose first 254 bytes sit in that sector; the table it prints is learned from the image, never assumed.
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
#!/usr/bin/env python3
|
|
375
|
+
# Decode one track of a G64 and derive the 4-to-5 GCR table from known bytes.
|
|
376
|
+
# usage: gcr_g64.py disk.g64 TRACK SECTOR [file whose first 254 bytes sit in SECTOR]
|
|
377
|
+
import struct, sys
|
|
378
|
+
|
|
379
|
+
img = open(sys.argv[1], "rb").read()
|
|
380
|
+
trk, sec = int(sys.argv[2]), int(sys.argv[3])
|
|
381
|
+
n = img[9]
|
|
382
|
+
offs = [struct.unpack_from("<I", img, 12 + 4 * i)[0] for i in range(n)]
|
|
383
|
+
zone = [struct.unpack_from("<I", img, 12 + 4 * n + 4 * i)[0] for i in range(n)]
|
|
384
|
+
print(img[:8], "version", img[8], "entries", n, "max", struct.unpack_from("<H", img, 10)[0])
|
|
385
|
+
for i in range(0, n, 2):
|
|
386
|
+
if offs[i]:
|
|
387
|
+
print("track", 1 + i // 2, "len", struct.unpack_from("<H", img, offs[i])[0], "zone", zone[i])
|
|
388
|
+
|
|
389
|
+
o = offs[(trk - 1) * 2]
|
|
390
|
+
tb = img[o + 2:o + 2 + struct.unpack_from("<H", img, o)[0]]
|
|
391
|
+
bits = "".join(f"{b:08b}" for b in tb)
|
|
392
|
+
|
|
393
|
+
syncs, i = [], 0 # runs of ten or more 1 bits
|
|
394
|
+
while i < len(bits):
|
|
395
|
+
j = i
|
|
396
|
+
while j < len(bits) and bits[j] == "1":
|
|
397
|
+
j += 1
|
|
398
|
+
if j - i >= 10:
|
|
399
|
+
syncs.append((i, j))
|
|
400
|
+
i = j + 1
|
|
401
|
+
print("syncs", len(syncs))
|
|
402
|
+
|
|
403
|
+
def codes(start, nbytes): # 5-bit groups for nbytes decoded bytes
|
|
404
|
+
return [int(bits[start + 5 * k:start + 5 * k + 5], 2) for k in range(2 * nbytes)]
|
|
405
|
+
|
|
406
|
+
table = {}
|
|
407
|
+
def learn(cs, known): # known: list of byte values or None
|
|
408
|
+
for k, b in enumerate(known):
|
|
409
|
+
if b is not None:
|
|
410
|
+
for c, nib in ((cs[2 * k], b >> 4), (cs[2 * k + 1], b & 15)):
|
|
411
|
+
assert table.setdefault(c, nib) == nib, "table conflict"
|
|
412
|
+
|
|
413
|
+
blocks = [(e, (syncs[k + 1][0] if k + 1 < len(syncs) else len(bits)) - e)
|
|
414
|
+
for k, (s, e) in enumerate(syncs)]
|
|
415
|
+
hdrs = [b for b in blocks if b[1] < 2000]
|
|
416
|
+
for start, _ in hdrs: # ID, checksum, sector, track, ID2, ID1, $0F, $0F
|
|
417
|
+
learn(codes(start, 8), [0x08, None, None, trk, None, None, 0x0F, 0x0F])
|
|
418
|
+
for start, _ in blocks:
|
|
419
|
+
if _ >= 2000:
|
|
420
|
+
learn(codes(start, 1), [0x07])
|
|
421
|
+
|
|
422
|
+
def decode(cs):
|
|
423
|
+
return bytes((table[cs[k]] << 4) | table[cs[k + 1]] for k in range(0, len(cs), 2))
|
|
424
|
+
|
|
425
|
+
want = None
|
|
426
|
+
for k, (s, e) in enumerate(syncs):
|
|
427
|
+
cs = codes(e, 8) # sector byte is codes 4 and 5
|
|
428
|
+
if (e, blocks[k][1]) in hdrs and (table.get(cs[4]), table.get(cs[5])) == (sec >> 4, sec & 15):
|
|
429
|
+
want = k
|
|
430
|
+
if len(sys.argv) > 4: # learn the rest from the known file
|
|
431
|
+
dstart = syncs[want + 1][1]
|
|
432
|
+
learn(codes(dstart, 260), [None, None, None] + list(open(sys.argv[4], "rb").read()[:254]) + [None, 0, 0])
|
|
433
|
+
print("table:", " ".join(f"{nib:X}={c:05b}" for c, nib in sorted(table.items(), key=lambda t: t[1])))
|
|
434
|
+
|
|
435
|
+
for start, _ in hdrs:
|
|
436
|
+
h = decode(codes(start, 8))
|
|
437
|
+
print("header", h.hex(" "), "checksum", "ok" if h[1] == h[2] ^ h[3] ^ h[4] ^ h[5] else "BAD")
|
|
438
|
+
d = decode(codes(syncs[want + 1][1], 260))
|
|
439
|
+
x = 0
|
|
440
|
+
for b in d[1:257]:
|
|
441
|
+
x ^= b
|
|
442
|
+
print("data", d.hex(" "), "\nchecksum", hex(d[257]), "computed", hex(x))
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Run as `python3 gcr_g64.py disk.g64 17 0 known.prg` it printed the table above, 21 headers each `checksum ok`, and the data block with `checksum 0x12 computed 0x12`. The ROM addresses were found by searching the ROM image for the byte runs quoted, with the file offset plus `$C000` as the address.
|
|
446
|
+
|
|
447
|
+
**Typical use:** preserving copy-protected originals, testing fastloader sync timing, demoscene releases that rely on non-standard sector ordering or gap manipulation.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### .NIB — Nibbler-format disk image (preserves bit-level timing)
|
|
452
|
+
|
|
453
|
+
**Consumed by:** vice, c1541
|
|
454
|
+
|
|
455
|
+
The NIB format (produced by the Nibbler hardware device and associated PC software) captures raw GCR bit streams from a 1541, including bit-level flux timing variation. Where G64 stores cleaned-up GCR byte streams padded to a fixed maximum size, NIB stores the actual byte-level content read directly from the disk surface at a fixed 8,192 bytes per half-track regardless of the track's natural length.
|
|
456
|
+
|
|
457
|
+
The file contains 84 entries (42 tracks × 2 half-tracks), each exactly 8,192 bytes, for a fixed file size of 688,128 bytes. There is no file header; track data begins at offset 0. Track 1 data at offset 0, track 1.5 data at offset 8,192, track 2 at offset 16,384, and so on.
|
|
458
|
+
|
|
459
|
+
NIB is used almost exclusively for archival of copy-protected originals where flux timing differences between sectors encode protection information that G64 cannot capture. The format is produced by the physical Nibbler hardware device and the `mnib` software tool; it cannot be generated by standard assembler toolchains. VICE can read NIB images via its `diskcontents` handler.
|
|
460
|
+
|
|
461
|
+
**Typical use:** archival of physically copy-protected disks; supplied to VICE for testing loaders against original protection timing.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Tape Images
|
|
466
|
+
|
|
467
|
+
Tape images represent the content of Commodore Datasette cassette recordings. The C64 KERNAL routines LOAD and SAVE interact with tape via the cassette port (CIA1 `$DC04`/`$DC05` timer and `$DC0D` ICR). See `../hardware/kernal-routines-reference.md` for LOAD and SAVE details.
|
|
468
|
+
|
|
469
|
+
### .T64 — Tape archive (PRG container)
|
|
470
|
+
|
|
471
|
+
**Consumed by:** vice
|
|
472
|
+
|
|
473
|
+
The T64 format was designed by the C64S emulator as a container for one or more C64 programs, nominally sourced from tape. Despite the name, T64 does not represent the tape data stream — it is a directory-based archive of PRG files with load/end addresses.
|
|
474
|
+
|
|
475
|
+
**File header (64 bytes):**
|
|
476
|
+
|
|
477
|
+
| Offset | Size | Field |
|
|
478
|
+
|--------|------|-------|
|
|
479
|
+
| $0000–$001F | 32 | Tape descriptor string (`"C64S tape image file"`, null-padded) |
|
|
480
|
+
| $0020–$0021 | 2 | Tape version (`$0100` or `$0101`, little-endian) |
|
|
481
|
+
| $0022–$0023 | 2 | Maximum directory entries (little-endian) |
|
|
482
|
+
| $0024–$0025 | 2 | Used directory entries (little-endian) |
|
|
483
|
+
| $0026–$0027 | 2 | Reserved |
|
|
484
|
+
| $0028–$003F | 24 | Tape container name (PETASCII, `$20`-padded) |
|
|
485
|
+
|
|
486
|
+
**Directory entries** (32 bytes each, starting at offset `$0040`):
|
|
487
|
+
|
|
488
|
+
| Offset | Size | Field |
|
|
489
|
+
|--------|------|-------|
|
|
490
|
+
| $00 | 1 | C64s file type: 0=free, 1=normal tape, 3=memory snapshot |
|
|
491
|
+
| $01 | 1 | 1541 file type (`$82`=PRG; any non-zero treated as PRG) |
|
|
492
|
+
| $02–$03 | 2 | Load address (little-endian) |
|
|
493
|
+
| $04–$05 | 2 | End address (little-endian; exclusive — first byte NOT part of file) |
|
|
494
|
+
| $06–$07 | 2 | Reserved |
|
|
495
|
+
| $08–$0B | 4 | Absolute file offset in container (little-endian) |
|
|
496
|
+
| $0C–$0F | 4 | Reserved |
|
|
497
|
+
| $10–$1F | 16 | Filename (PETASCII, `$20`-padded) |
|
|
498
|
+
|
|
499
|
+
File data is stored sequentially after the directory, pointed to by each entry's offset field. File length = end address − load address.
|
|
500
|
+
|
|
501
|
+
**Known limitations:** T64 does not support multi-load programs, REL files, or files with load address `$0000`. Some early C64S-produced T64 files contain a buggy end address of `$C3C6` regardless of actual file size; VICE works around this by reading the actual file size from the container. Multi-file T64 archives are supported in principle but many authoring tools limited themselves to single-file containers.
|
|
502
|
+
|
|
503
|
+
**Typical use:** distributing single PRG files as a tape equivalent; legacy emulator interchange.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
### .TAP — Raw tape pulse-width data
|
|
508
|
+
|
|
509
|
+
**Consumed by:** vice
|
|
510
|
+
|
|
511
|
+
The TAP format records the raw pulse-width timing of a Commodore Datasette tape recording. Unlike T64, TAP faithfully represents the actual cassette data stream, including turbo loaders, custom protection schemes, and non-standard encoding. This makes it the preferred format for archival and protection research.
|
|
512
|
+
|
|
513
|
+
**File header (20 bytes):**
|
|
514
|
+
|
|
515
|
+
| Offset | Size | Field |
|
|
516
|
+
|--------|------|-------|
|
|
517
|
+
| $0000–$000B | 12 | Signature `"C64-TAPE-RAW"` |
|
|
518
|
+
| $000C | 1 | Version: `$00` (original) or `$01` (extended) |
|
|
519
|
+
| $000D–$000F | 3 | Reserved (zero) |
|
|
520
|
+
| $0010–$0013 | 4 | Data length in bytes, little-endian (excludes this 20-byte header) |
|
|
521
|
+
|
|
522
|
+
**Data section** (immediately follows header):
|
|
523
|
+
|
|
524
|
+
Each byte represents a pulse: the duration is `(byte_value × 8) / 985,248` seconds under the PAL clock (985,248 Hz). The NTSC clock (1,022,727 Hz) produces slightly different timing, but the format stores raw cycle counts so VICE applies the correct clock for the target region.
|
|
525
|
+
|
|
526
|
+
**Version `$00`:** A data byte of `$00` signals a pulse overflow (duration > 255 × 8 cycles); the actual duration is unspecified and varies by implementation.
|
|
527
|
+
|
|
528
|
+
**Version `$01`:** A data byte of `$00` is followed by 3 additional bytes giving the true pulse duration as a 24-bit little-endian cycle count. This extension handles long pauses and turbo-loader timing precisely.
|
|
529
|
+
|
|
530
|
+
Standard KERNAL tape encoding uses three pulse lengths, and a data bit is a pair of pulses, not one pulse. **Correction (2026-09-23).** This paragraph used to say the KERNAL used "two pulse lengths: short (~370 µs, PAL) for a 0 bit, long (~530 µs) for a 1 bit". A decoder written from that sentence reads garbage from every KERNAL tape: the two figures it gave are roughly the short and medium pulses, the long pulse was missing, and no single pulse carries a bit. The subsection below replaces it, from a SAVE recorded in VICE. Turbo loaders (e.g., FINISH, Novaload, Freeload) use completely different encoding schemes, all of which TAP preserves faithfully.
|
|
531
|
+
|
|
532
|
+
#### KERNAL bit encoding
|
|
533
|
+
|
|
534
|
+
Every figure in this subsection was measured on the windowless x64sc build of VICE 3.10 (PAL, `-warp`): a writable TAP was attached with `-1`, the monitor command `tapectrl 4` pressed RECORD, and `-keybuf` typed `10 rem abc` and `save"t",1`. The TAP came back at version 1 with 41,802 data bytes, byte-identical on a second run, and a Python reader decoded all 448 bytes of it with no parity failure. VICE records a falling edge at each `1` to `0` change of the write line, so one TAP entry is one complete KERNAL pulse, low half and high half together.
|
|
535
|
+
|
|
536
|
+
**Pulse lengths.** The pulse bytes fall into three clusters and nothing lies between them:
|
|
537
|
+
|
|
538
|
+
| Pulse | TAP bytes seen | Most common | Cycles (PAL) | Duration | Role |
|
|
539
|
+
|-------|---------------|-------------|--------------|----------|------|
|
|
540
|
+
| Short (S) | `$2C`–`$31` | `$2F` (20,191 of 36,862) | 352–392, mode 376 | about 382 µs | leader, sync, first half of a 0 bit, second half of a 1 bit |
|
|
541
|
+
| Medium (M) | `$40`–`$44` | `$43` (2,172 of 4,480) | 512–544, mode 536 | about 544 µs | second half of a 0 bit, first half of a 1 bit, second half of the byte marker |
|
|
542
|
+
| Long (L) | `$56`–`$59` | `$58` (279 of 452) | 688–712, mode 704 | about 715 µs | first half of the byte marker and of the end-of-block marker |
|
|
543
|
+
|
|
544
|
+
The spread inside each cluster is the KERNAL's own jitter, not a TAP artefact: the write interrupt reprograms CIA 1 Timer B from software, so an interrupt that is served a few cycles late lengthens the pulse by a few cycles. A reader should classify by threshold (below `$3A` short, `$3A`–`$4B` medium, above `$4B` long worked here), never by exact value.
|
|
545
|
+
|
|
546
|
+
**Bit and byte layout.** Each unit of the stream is a pulse pair:
|
|
547
|
+
|
|
548
|
+
| Pair | Meaning |
|
|
549
|
+
|------|---------|
|
|
550
|
+
| S then M | data bit 0 |
|
|
551
|
+
| M then S | data bit 1 |
|
|
552
|
+
| L then M | byte marker: a data byte follows |
|
|
553
|
+
| L then S | end-of-block marker: no more bytes in this copy |
|
|
554
|
+
|
|
555
|
+
A byte is 20 pulses: the marker pair, eight data-bit pairs least significant bit first, and a parity pair. Parity is odd: the parity bit is chosen so that the nine bits together hold an odd number of ones. The first byte of the header block, `$89`, was recorded as the pulse bytes `57 41 43 2E 2F 42 2E 43 42 2F 2F 42 2F 42 30 43 42 2E 2F 41`, which read as `LM MS SM SM MS SM SM SM MS SM`: marker, bits `1 0 0 1 0 0 0 1` (`$89` with bit 0 first), parity 0 because the data already holds three ones.
|
|
556
|
+
|
|
557
|
+
**Block structure.** A block is written twice in a row, and each copy is preceded by a nine-byte countdown that tells the reader which copy it is:
|
|
558
|
+
|
|
559
|
+
| Element | Pulses or bytes | Measured |
|
|
560
|
+
|---------|-----------------|----------|
|
|
561
|
+
| Leader before the header block | short pulses | 27,137 shorts, about 10.4 s |
|
|
562
|
+
| Countdown, first copy | 9 bytes | `$89 $88 $87 $86 $85 $84 $83 $82 $81` |
|
|
563
|
+
| Block data | n bytes | header: 192 bytes; program: 12 bytes (`$0801`–`$080C`) |
|
|
564
|
+
| Checksum | 1 byte | XOR of the data bytes: `$59` for the header, `$E6` for the program |
|
|
565
|
+
| End-of-block marker | L then S | present after every copy |
|
|
566
|
+
| Gap between the two copies | short pulses | 79 shorts |
|
|
567
|
+
| Countdown, second copy | 9 bytes | `$09 $08 $07 $06 $05 $04 $03 $02 $01` |
|
|
568
|
+
| Trailer after the second copy | short pulses | 78 shorts |
|
|
569
|
+
| Silence between header and program | one 24-bit TAP entry | 327,689 cycles, about 0.33 s |
|
|
570
|
+
| Leader before the program block | short pulses | 5,376 shorts, about 2.0 s |
|
|
571
|
+
|
|
572
|
+
The header block's 192 bytes were: type `$01` (relocatable BASIC program), start address `$0801` and end address `$080D` little-endian, the filename `T` and 186 bytes of `$20` padding. The program block held the twelve bytes of `10 REM ABC` exactly as they sit in memory, with the end address exclusive. One further entry sits at the very start of the file, 472,967 cycles long: the time from pressing RECORD at boot to the first pulse, an artefact of the run and not of the format. Which of the two copies the KERNAL reads on LOAD, and how a read error in one is repaired from the other, were not measured here.
|
|
573
|
+
|
|
574
|
+
**Typical use:** archival of original cassette software; testing turbo loader implementations; copy-protection analysis.
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Music
|
|
579
|
+
|
|
580
|
+
### .SID — PSID/RSID music file
|
|
581
|
+
|
|
582
|
+
**Consumed by:** vice, sidplayfp, kickassembler
|
|
583
|
+
|
|
584
|
+
The SID format is a standard container for C64 music, combining a short metadata header with a C64 binary containing the init and play routines. The format exists in two variants: PSID (Portable SID) for files that run under emulated environments, and RSID (Real SID) for files that require an authentic C64 environment (real interrupt timing, BASIC ROM, etc.).
|
|
585
|
+
|
|
586
|
+
**Note on producers:** SID files are not produced by the assembler toolchains in this knowledge base. The canonical producers are dedicated C64 music trackers: GoatTracker 2 (cross-platform, exports PSID/RSID), SID-Wizard (native C64 tracker), and DefMON. These tools are not currently represented as Tool nodes in this KB. KickAssembler can *consume* SID files via the `LoadSid` directive to embed a SID player's binary into a larger program, but it does not produce `.sid` files.
|
|
587
|
+
|
|
588
|
+
**File header:**
|
|
589
|
+
|
|
590
|
+
| Offset | Size | Field |
|
|
591
|
+
|--------|------|-------|
|
|
592
|
+
| $00–$03 | 4 | Magic: `"PSID"` or `"RSID"` |
|
|
593
|
+
| $04–$05 | 2 | Version: `$0001` (v1), `$0002` (v2), `$0003` (v3) or `$0004` (v4) — big-endian; RSID must be 2, 3 or 4 |
|
|
594
|
+
| $06–$07 | 2 | Data offset: `$0076` (v1) or `$007C` (v2, v3 and v4) — big-endian |
|
|
595
|
+
| $08–$09 | 2 | Load address (0 = embedded in first 2 bytes of data, little-endian) |
|
|
596
|
+
| $0A–$0B | 2 | Init address (0 = load address; called with song number in A) |
|
|
597
|
+
| $0C–$0D | 2 | Play address (0 = init installs IRQ handler; must be 0 for RSID) |
|
|
598
|
+
| $0E–$0F | 2 | Number of songs (1–256) — big-endian |
|
|
599
|
+
| $10–$11 | 2 | Default start song (1-based) — big-endian |
|
|
600
|
+
| $12–$15 | 4 | Speed flags: each bit governs one song. 0=VBI (50/60 Hz), 1=CIA1 timer (~60 Hz) |
|
|
601
|
+
| $16–$35 | 32 | Song name (null-terminated ASCII, max 31 chars) |
|
|
602
|
+
| $36–$55 | 32 | Author name (null-terminated ASCII) |
|
|
603
|
+
| $56–$75 | 32 | Released/copyright (null-terminated ASCII) |
|
|
604
|
+
|
|
605
|
+
**Version 2, 3 and 4 extensions (offsets $76–$7B):**
|
|
606
|
+
|
|
607
|
+
| Offset | Size | Field |
|
|
608
|
+
|--------|------|-------|
|
|
609
|
+
| $76–$77 | 2 | Flags, big-endian: bit 0=MUS data, bit 1=PlaySID-specific (PSID) or C64 BASIC (RSID), bits 2-3=video standard (00=unknown, 01=PAL, 10=NTSC, 11=both), bits 4-5=first SID model (00=unknown, 01=6581, 10=8580, 11=both), bits 6-7=second SID model (v3 and later; same codes, 00 meaning "same as the first SID"), bits 8-9=third SID model (v4; same codes, 00 meaning "same as the first SID"), bits 10-15 reserved |
|
|
610
|
+
| $78 | 1 | Start page (relocation page; 0=clean, $FF=no free pages) |
|
|
611
|
+
| $79 | 1 | Page length (number of free pages for relocation) |
|
|
612
|
+
| $7A | 1 | Second SID address (v3 and later): the middle byte of `$Dxx0`, so the chip sits at `$D000 + byte × 16`. Valid values `$42`–`$7F` and `$E0`–`$FE`, even only, which is `$D420`–`$D7E0` and `$DE00`–`$DFE0`. Zero or any invalid value means no second SID. Must be 0 in v2 |
|
|
613
|
+
| $7B | 1 | Third SID address (v4): the same encoding and ranges as `$7A`, and it must differ from `$7A`. Zero means no third SID. Must be 0 in v2 and v3 |
|
|
614
|
+
|
|
615
|
+
**Correction (2026-09-23).** This table used to stop at version 2 and call `$7A`–`$7B` "Reserved (zero)", and the version row listed only `$0001` and `$0002`. Versions 3 and 4 put the second and third SID addresses in those two bytes and the two extra model fields in the flags word, so a reader written from the old table would play every two-SID and three-SID tune on one chip. The rows above follow the HVSC document `SID_file_format.txt` (in the collection's `DOCUMENTS` directory), and were checked two ways: against HVSC Release 84, and against VICE 3.10's `vsid`. Both are described under "Measured here" below.
|
|
616
|
+
|
|
617
|
+
**Which fields each version has:**
|
|
618
|
+
|
|
619
|
+
| Field | v1 | v2 | v3 | v4 |
|
|
620
|
+
|-------|----|----|----|----|
|
|
621
|
+
| Magic through the three strings (`$00`–`$75`) | yes | yes | yes | yes |
|
|
622
|
+
| Flags bits 0-5, start page, page length (`$76`–`$79`) | absent | yes | yes | yes |
|
|
623
|
+
| Second SID address (`$7A`) and flags bits 6-7 | absent | must be 0 | yes | yes |
|
|
624
|
+
| Third SID address (`$7B`) and flags bits 8-9 | absent | must be 0 | must be 0 | yes |
|
|
625
|
+
| Data offset | `$0076` | `$007C` | `$007C` | `$007C` |
|
|
626
|
+
|
|
627
|
+
**Version history.** Version 1 is Michael Schwendt's original header for SIDPLAY, 118 bytes ending at `$75`. Version 2 added the six bytes at `$76`; the "v2NG" extension by Simon White and Dag Lem gave most of them their meaning (flag bits 1 to 5, start page, page length) and defined RSID, and it kept `$0002` as the version number, so v2 and v2NG cannot be told apart from the header. Wilfred Bos added the second SID address and its model bits as version 3, and the third SID address and its model bits as version 4. The header length has not changed since version 2: the data starts at `$7C` in every file the collection holds. The HVSC document names these authors and gives no dates, so none are given here.
|
|
628
|
+
|
|
629
|
+
**PSID and RSID.** The magic says what the tune may assume. A PSID tune is driven by the player: the player calls init with the song number in A, then calls play on every VBI (speed bit 0) or on CIA 1 timer A (speed bit 1), and before each call it writes `$01` from the routine's address (`$37` below `$A000`, `$36` below `$D000`, `$35` at `$E000` and above, `$34` inside the `$D000` page). So a PSID tune should not depend on which ROMs are mapped; with a non-zero play address the player does the timing, and with play address 0 its init routine installs the interrupt handler itself, as the table above says. An RSID tune gets the power-on machine and nothing more: `$01` = `$37`, CIA 1 timer A running at 60 Hz with its interrupt enabled, the VIC raster interrupt set to line `$137` but not enabled, and the tune must set up its own interrupt source and handler. That is why RSID pins header fields: the version must be 2, 3 or 4; load address, play address and speed must all be 0; the embedded load address must be at or above `$07E8`; and init must not sit in a ROM or I/O window (`$A000`–`$BFFF`, `$D000`–`$FFFF`). Flag bit 1 is "PlaySID-specific" in PSID and "C64 BASIC" in RSID; with it set, the player puts the song number in `$030C` and runs the tune as a BASIC program, and the init address must then be 0. A player that finds an RSID field outside these rules must reject the file. An earlier version of this paragraph said RSID "requires the C64 BASIC ROM"; both formats have the ROMs present, and the difference is what the tune may rely on. The same contrast, from the tune's side, is in [music-sid.md](../techniques/music-sid.md).
|
|
630
|
+
|
|
631
|
+
The SID collection at HVSC (High Voltage SID Collection) is the reference corpus for this format. HVSC Release 84 holds 60,572 files: 56,349 PSID v2, 302 PSID v3, 25 PSID v4, 3,885 RSID v2 and 11 RSID v3, and no version-1 file.
|
|
632
|
+
|
|
633
|
+
**Measured here (2026-09-23).** The script below wrote a PSID v2, v3 and v4 header by hand from the tables above, over a body of two `RTS` routines, and read each one back; every field came back as written. A v3 with `$42` at `$7A` decoded to `$D420`, a v4 with `$42` and `$44` to `$D420` and `$D440`, and a v4 with `$E0` and `$F0` to `$DE00` and `$DF00`. The same reader decoded a two-SID file from the collection to `$DE00` with second-model bits 10 (8580). Across the 338 version-3 and version-4 files in the collection every `$7A` and `$7B` value is even and inside the valid ranges, no version-3 file has a non-zero `$7B`, and no version-4 file has `$7B` equal to `$7A`. VICE 3.10's `vsid` (the windowless build, run with the command below and a monitor script that reads its resources after the load) accepted all three hand-made files, logged `PSID version number: 2`, `3` and `4`, `2nd SID at $d420` and `3rd SID at $d440`, and set `SidStereo` to 1 for the v3 file and 2 for the v4 file with `Sid2AddressStart` 54304 (`$D420`) and `Sid3AddressStart` 54336 (`$D440`); the `$E0`/`$F0` file gave 56832 and 57088 (`$DE00`, `$DF00`). Four negatives behaved as the table says: a v2 header with `$42` at `$7A` left `SidStereo` at 0, and a v3 header with `$41` (`$D410`, odd), `$80` (`$D800`) or `$D8` (`$DD80`) at `$7A` logged the address but left `SidStereo` at 0, so an invalid value means no second SID. One divergence: a v3 header with `$44` at `$7B` set `SidStereo` to 2 and `Sid3AddressStart` to `$D440`, so VICE 3.10 reads the third SID byte from any version-3 file where the document reserves it for version 4; write 0 there in a v3 file. VICE's `psid.c` never reads bits 6-9; a comment there notes where they sit, and every chip gets the first SID's model. That is from its source, not measured here by ear.
|
|
634
|
+
|
|
635
|
+
```text
|
|
636
|
+
vsid -default -directory <VICE data dir> -console -warp +sound -limitcycles 1000000 -moncommands mon.txt tune.sid
|
|
637
|
+
# mon.txt
|
|
638
|
+
resourceget "SidStereo"
|
|
639
|
+
resourceget "Sid2AddressStart"
|
|
640
|
+
resourceget "Sid3AddressStart"
|
|
641
|
+
x
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
```text
|
|
645
|
+
# psidhdr.py: write a PSID v2/v3/v4 header by hand, then read it back.
|
|
646
|
+
import struct
|
|
647
|
+
|
|
648
|
+
def sid_byte(addr): # $D420 -> $42, 0 -> 0
|
|
649
|
+
return 0 if addr == 0 else (addr >> 4) & 0xFF
|
|
650
|
+
|
|
651
|
+
def sid_addr(b): # $42 -> $D420, 0 -> 0
|
|
652
|
+
return 0 if b == 0 else 0xD000 | (b << 4)
|
|
653
|
+
|
|
654
|
+
def sid_ok(addr):
|
|
655
|
+
return addr != 0 and (addr & 0x10) == 0 and (0xD420 <= addr < 0xD800 or addr >= 0xDE00)
|
|
656
|
+
|
|
657
|
+
def write(version, sid2=0, sid3=0, model2=0, model3=0):
|
|
658
|
+
flags = (1 << 2) | (1 << 4) # PAL, 6581
|
|
659
|
+
if version >= 3: flags |= (model2 & 3) << 6
|
|
660
|
+
if version >= 4: flags |= (model3 & 3) << 8
|
|
661
|
+
h = bytearray(b"PSID")
|
|
662
|
+
h += struct.pack(">HH", version, 0x76 if version == 1 else 0x7C)
|
|
663
|
+
h += struct.pack(">HHH", 0, 0x1000, 0x1003) # load (embedded), init, play
|
|
664
|
+
h += struct.pack(">HHI", 1, 1, 0) # songs, start song, speed (VBI)
|
|
665
|
+
for s in (b"round trip", b"c64-kb", b"2026"):
|
|
666
|
+
h += s.ljust(32, b"\0")
|
|
667
|
+
if version >= 2:
|
|
668
|
+
h += struct.pack(">HBB", flags, 0, 0) # flags, start page, page length
|
|
669
|
+
h += bytes([sid_byte(sid2) if version >= 3 else 0,
|
|
670
|
+
sid_byte(sid3) if version >= 4 else 0])
|
|
671
|
+
return bytes(h) + b"\x00\x10" + b"\x60\xea\xea\x60" # $1000: RTS $1003: RTS
|
|
672
|
+
|
|
673
|
+
def read(d):
|
|
674
|
+
version = struct.unpack(">H", d[4:6])[0]
|
|
675
|
+
r = {"version": version, "data_offset": struct.unpack(">H", d[6:8])[0]}
|
|
676
|
+
if version >= 2:
|
|
677
|
+
flags = struct.unpack(">H", d[0x76:0x78])[0]
|
|
678
|
+
r["model1"] = (flags >> 4) & 3
|
|
679
|
+
if version >= 3:
|
|
680
|
+
r["model2"], r["sid2"] = (flags >> 6) & 3, sid_addr(d[0x7A])
|
|
681
|
+
r["sid2_ok"] = sid_ok(r["sid2"])
|
|
682
|
+
if version >= 4:
|
|
683
|
+
r["model3"], r["sid3"] = (flags >> 8) & 3, sid_addr(d[0x7B])
|
|
684
|
+
r["sid3_ok"] = sid_ok(r["sid3"])
|
|
685
|
+
return r
|
|
686
|
+
|
|
687
|
+
for v, s2, s3 in ((2, 0, 0), (3, 0xD420, 0), (4, 0xD420, 0xD440), (4, 0xDE00, 0xDF00)):
|
|
688
|
+
d = write(v, s2, s3, model2=2, model3=1)
|
|
689
|
+
back = read(d)
|
|
690
|
+
assert back["version"] == v and back.get("sid2", 0) == s2 and back.get("sid3", 0) == s3, back
|
|
691
|
+
open(f"v{v}-{s2:04x}.sid", "wb").write(d)
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
### .SNG — GoatTracker 2 song
|
|
697
|
+
|
|
698
|
+
The editor's own save format: the song as the composer edits it, before the packer/relocator strips and packs it. Nothing in this knowledge base's Tool node set produces or consumes it; GoatTracker 2 writes it with F11 and reads it back, and the relocator (F9 in the editor, or the standalone `gt2reloc`) turns it into a `.prg`, `.bin` or `.sid` (`../art/asset-pipelines.md`, "Music: GoatTracker"). It sits here beside `.SID` because that is the format it is exported to. Every count below comes from the save routine in GoatTracker 2.77's `gsong.c` and the constants in `gcommon.h`; the layout was then checked by parsing the fourteen `.sng` files in the distribution's `examples/` directory with a script written from this table, and each one was consumed to exactly its file length. GoatTracker 2 writes `GTS5` and loads `GTS2` to `GTS5`; only `GTS5` is described here.
|
|
699
|
+
|
|
700
|
+
All multi-byte fields are byte sequences, not integers: there is no endianness in the file. Every count is one byte, so no list has more than 255 entries.
|
|
701
|
+
|
|
702
|
+
**Header (101 bytes):**
|
|
703
|
+
|
|
704
|
+
| Offset | Size | Field |
|
|
705
|
+
|--------|------|-------|
|
|
706
|
+
| $00–$03 | 4 | Identifier `"GTS5"` |
|
|
707
|
+
| $04–$23 | 32 | Song name, zero-padded |
|
|
708
|
+
| $24–$43 | 32 | Author name, zero-padded |
|
|
709
|
+
| $44–$63 | 32 | Copyright string, zero-padded |
|
|
710
|
+
| $64 | 1 | Number of subtunes `s` (1–32) |
|
|
711
|
+
|
|
712
|
+
**Order lists:** one record per channel, channels 1, 2, 3 of subtune 0, then channels 1, 2, 3 of subtune 1, and so on for `s` subtunes.
|
|
713
|
+
|
|
714
|
+
| Offset | Size | Field |
|
|
715
|
+
|--------|------|-------|
|
|
716
|
+
| +0 | 1 | Length `n`: the number of order-list bytes up to and including the `$FF` end mark |
|
|
717
|
+
| +1 | n+1 | Order list: `$00–$CF` pattern number, `$D0–$DF` repeat, `$E0–$EF` transpose down, `$F0–$FE` transpose up, `$FF` end mark; the byte after the end mark is the restart position |
|
|
718
|
+
|
|
719
|
+
**Instruments:** a count byte, then one 25-byte record per instrument from instrument 1 up to the highest one that has a non-zero parameter or is named in a pattern. Instrument 0, the empty instrument, is never stored.
|
|
720
|
+
|
|
721
|
+
| Offset | Size | Field |
|
|
722
|
+
|--------|------|-------|
|
|
723
|
+
| +0 | 1 | Attack/decay |
|
|
724
|
+
| +1 | 1 | Sustain/release |
|
|
725
|
+
| +2 | 1 | Wave table pointer (row + 1; 0 = none) |
|
|
726
|
+
| +3 | 1 | Pulse table pointer |
|
|
727
|
+
| +4 | 1 | Filter table pointer |
|
|
728
|
+
| +5 | 1 | Speed table pointer (vibrato parameter) |
|
|
729
|
+
| +6 | 1 | Vibrato delay |
|
|
730
|
+
| +7 | 1 | Gate-off timer |
|
|
731
|
+
| +8 | 1 | Hard-restart / first-frame waveform |
|
|
732
|
+
| +9 | 16 | Instrument name, zero-padded |
|
|
733
|
+
|
|
734
|
+
**Tables:** four records in this order: wave, pulse, filter, speed. A table's stored length is the index of its last non-zero row plus one, so an empty table is a single zero byte.
|
|
735
|
+
|
|
736
|
+
| Offset | Size | Field |
|
|
737
|
+
|--------|------|-------|
|
|
738
|
+
| +0 | 1 | Row count `r` (0–255) |
|
|
739
|
+
| +1 | r | Left column, rows 0 to r-1 |
|
|
740
|
+
| +1+r | r | Right column, rows 0 to r-1 |
|
|
741
|
+
|
|
742
|
+
**Patterns:** a count byte `p` (patterns 0 to p-1, where p-1 is the highest pattern that has content or is named in an order list), then one record per pattern.
|
|
743
|
+
|
|
744
|
+
| Offset | Size | Field |
|
|
745
|
+
|--------|------|-------|
|
|
746
|
+
| +0 | 1 | Row count `m`, including the end row (up to 129) |
|
|
747
|
+
| +1 | m×4 | Rows of four bytes: note, instrument (`$00–$3F`), command (`$00–$0F`), command data |
|
|
748
|
+
|
|
749
|
+
Note byte values: `$60–$BC` are the notes C-0 to G#7, `$BD` rest, `$BE` key off, `$BF` key on, `$FF` the end row. A 64-row pattern is therefore stored as 65 rows, 260 bytes. The commands are `1` portamento up, `2` portamento down, `3` tone portamento, `4` vibrato, `5` set AD, `6` set SR, `7` set waveform, `8`, `9`, `A` set wave, pulse or filter table pointer, `B` set filter control, `C` set filter cutoff, `D` master volume, `E` funktempo and `F` set tempo, all read from `gcommon.h`.
|
|
750
|
+
|
|
751
|
+
Decoded from `examples/consultant.sng` (3,060 bytes): bytes 0–7 are `47 54 53 35 54 68 65 20` (`GTS5` then `The `); name `The Consultant`, author `Cadaver`, copyright `2002 Covert Bitops`; one subtune whose three order lists are 78, 17 and 17 bytes long; eight instruments, the first named `BD+Bass` with AD `$09`, SR `$BB`; table lengths 31, 15, 6 and 4 rows; eleven patterns, the longest 73 rows including the end row. Packed by the relocator with defaults, this song becomes 1,786 bytes at `$1000`.
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
|
|
755
|
+
## Graphics Assets
|
|
756
|
+
|
|
757
|
+
Project files from the two editors most C64 artists hand over: CharPad for character sets, tiles and maps, SpritePad for sprites. Both are the editor's own save format, not a raw export, so a header and per-section framing sit in front of the bytes a program wants. Oscar64's `#embed` reads both directly (`../toolchains/oscar64-reference.md`); every other toolchain in this KB wants the editor's raw binary export, or a converter. `../art/asset-pipelines.md` covers the pipeline side.
|
|
758
|
+
|
|
759
|
+
Every figure below that is not marked otherwise was decoded in Python from a file on disk and checked against that file's length. The sample set: five CharPad version 8 files from the Corescape source tree (`background.ctm` 1,056 bytes, `introfont.ctm` 2,602, `statusfont.ctm` 2,602, `scorefont.ctm` 2,170, `tiles.ctm` 12,455), one CharPad version 5 file from the Death Weapon source tree (`Background.ctm`, 7,372 bytes), two SpritePad version 5 files (Oscar64's `samples/resources/mouse.spd`, 1,044 bytes; Corescape's `sprites.spd`, 8,284 bytes) and one SpritePad file with no signature at all (Death Weapon's `Sprites.spd`, 6,147 bytes). No version 9 file was found on this machine; the version 9 layout is read from Oscar64's own reader (`oscar64/Preprocessor.cpp`, release 1.32.271) and is marked as such.
|
|
760
|
+
|
|
761
|
+
### .CTM — CharPad character set, tiles and map
|
|
762
|
+
|
|
763
|
+
**Consumed by:** oscar64
|
|
764
|
+
|
|
765
|
+
Oscar64 reads it through `#embed` with the specifiers `ctm_chars`, `ctm_attr1`, `ctm_attr2`, `ctm_tiles8`, `ctm_tiles8sw`, `ctm_tiles16`, `ctm_map8` and `ctm_map16`; what each one yields is under "What Oscar64 emits" below. The file begins with the three ASCII bytes `CTM` and a version byte. Version 5 is the CharPad 2.x save; versions 8 and 9 are the Pro edition's, and they are a different shape: a short fixed header, then a run of sections, each one opened by a two-byte marker, in a fixed order, some of them present only when a header flag or the colouring method says so. A reader that assumes version 5's fixed 20-byte header on a version 8 file lands 2 bytes inside the first character (the character section starts at `$12`: 14 header bytes, a 2-byte marker and a 2-byte count), and 3 bytes short of it on a version 9 file.
|
|
766
|
+
|
|
767
|
+
**Fixed header:**
|
|
768
|
+
|
|
769
|
+
| Offset (v5) | Offset (v8) | Offset (v9) | Size | Field |
|
|
770
|
+
|-------------|-------------|-------------|------|-------|
|
|
771
|
+
| $00–$02 | $00–$02 | $00–$02 | 3 | Signature `CTM` |
|
|
772
|
+
| $03 | $03 | $03 | 1 | Version: `$05`, `$08` or `$09` |
|
|
773
|
+
| $04 | – | – | 1 | Background colour (v5 sample: `$00`) |
|
|
774
|
+
| $05 | – | – | 1 | Multicolour 1 (v5 sample: `$0B`) |
|
|
775
|
+
| $06 | – | – | 1 | Multicolour 2 (v5 sample: `$0C`) |
|
|
776
|
+
| $07 | – | – | 1 | Character colour (v5 sample: `$0C`) |
|
|
777
|
+
| – | $04 | $04 | 1 | Display mode: `0` hires text and `1` multicolour text in the samples; Oscar64 sizes colour cells at 2 bytes for mode `3` and 3 bytes for mode `4`, which fits hires and multicolour bitmap |
|
|
778
|
+
| $08 | $05 | $05 | 1 | Colouring method: `0` global, `1` per tile, `2` per character |
|
|
779
|
+
| $09 | $06 | $06 | 1 | Flags: bit 0 set means the file carries tiles (v5 sample: `$05`; v8 samples `$00` and `$01`) |
|
|
780
|
+
| – | – | $07–$08 | 2 | Grid width, little-endian (v9 only, not measured here) |
|
|
781
|
+
| – | – | $09–$0A | 2 | Grid height, little-endian (v9 only, not measured here) |
|
|
782
|
+
| – | – | $0B | 1 | Grid configuration (v9 only, not measured here) |
|
|
783
|
+
| – | $07–$0D | $0C–$12 | 7 | Seven colour bytes; Oscar64 skips them, and which byte is which is not measured here (the samples hold `0E 00 0F 0C 09 08 07`, `00 00 01 0C 07 08 07`, `09 00 07 0C 09 08 07`) |
|
|
784
|
+
| $0A–$0B | – | – | 2 | Character count minus one (v5 sample: `$00FF`, 256 characters) |
|
|
785
|
+
| $0C–$0D | – | – | 2 | Tile count minus one (v5 sample: `$007B`, 124 tiles) |
|
|
786
|
+
| $0E | – | – | 1 | Tile width in cells (v5 sample: 4) |
|
|
787
|
+
| $0F | – | – | 1 | Tile height in cells (v5 sample: 4) |
|
|
788
|
+
| $10–$11 | – | – | 2 | Map width in tiles, little-endian (v5 sample: 10) |
|
|
789
|
+
| $12–$13 | – | – | 2 | Map height in tiles, little-endian (v5 sample: 54) |
|
|
790
|
+
| header ends | $14 | $0E | $13 | | |
|
|
791
|
+
|
|
792
|
+
Version 5's four colour names at `$04`–`$07` are the CharPad 2 ordering as remembered, not measured here; the counts, the tile size and the map size at `$0A`–`$13` are measured, because the section sizes they imply walk the sample to its last byte (see below). Version 9's header is version 8's with five grid bytes inserted between the flags and the colours; Oscar64 reads it that way and treats the rest of the file identically. A sibling signature `CTT` with version 9 is a Pro tile set whose header carries six colour bytes rather than seven; Oscar64 accepts it, no sample was found, not measured here.
|
|
793
|
+
|
|
794
|
+
**Version 5 sections** follow the header with no framing, in this order, and the walk over the sample lands exactly on byte 7,372:
|
|
795
|
+
|
|
796
|
+
| Section | Present when | Size | Sample |
|
|
797
|
+
|---------|--------------|------|--------|
|
|
798
|
+
| Characters | always | 8 × characters | 2,048 at `$14` |
|
|
799
|
+
| Character attributes | always | 1 × characters (colour in the low nybble; the high nybble is 0 throughout the sample) | 256 at `$814` |
|
|
800
|
+
| Tiles | flags bit 0 | 2 × tiles × width × height, little-endian character indices | 3,968 at `$914`, largest index 248 |
|
|
801
|
+
| Tile colours | flags bit 0 and colouring method `1` | 1 × tiles | absent (method is `2`) |
|
|
802
|
+
| Map | always | 2 × width × height, little-endian tile indices | 1,080 at `$1894`, largest index 123 |
|
|
803
|
+
|
|
804
|
+
**Version 8 and 9 sections.** Each section opens with a two-byte marker. In every sample the marker bytes run `DA B0`, `DA B1`, `DA B2`, … in file order (read as little-endian words, `$B0DA`, `$B1DA`, `$B2DA`), so the second byte numbers the section's position in this particular file, not its kind: `background.ctm` has tiles under `DA B2` and its map under `DA B5`, while `introfont.ctm`, which has no tiles, has its map under `DA B2`. Oscar64 reads each marker and discards it. The order and the conditions, as Oscar64 walks them and as the five samples confirm:
|
|
805
|
+
|
|
806
|
+
| Order | Section | Present when | Section header | Data |
|
|
807
|
+
|-------|---------|--------------|----------------|------|
|
|
808
|
+
| 1 | Characters | always | marker, count minus one (2 bytes) | 8 × count |
|
|
809
|
+
| 2 | Character materials | always | marker | 1 × count |
|
|
810
|
+
| 3 | Character colours | colouring method `2` | marker | 1 × count; 2 × count in display mode `3`; 3 × count in display mode `4` |
|
|
811
|
+
| 4 | Tiles | flags bit 0 | marker, count minus one (2), width (1), height (1) | 2 × count × width × height, little-endian character indices |
|
|
812
|
+
| 5 | Tile colours | flags bit 0 and colouring method `1` | marker | 1 × tiles; 2 × or 3 × in display modes `3` and `4` |
|
|
813
|
+
| 6 | Tile tags | flags bit 0 | marker | 1 × tiles |
|
|
814
|
+
| 7 | Tile names | flags bit 0 | marker | one NUL-terminated string per tile |
|
|
815
|
+
| 8 | Map | always | marker, width (2), height (2) | 2 × width × height, little-endian indices (tiles when the file has them, characters otherwise) |
|
|
816
|
+
|
|
817
|
+
Walks over the five samples, each ending on the file's last byte:
|
|
818
|
+
|
|
819
|
+
- `background.ctm`: display `1`, method `0`, flags `$01`; 40 characters (320 bytes at `$12`), 40 materials, 10 tiles of 2×2 (80 bytes), 10 tags, 10 names in 90 bytes, map 20×12 (480 bytes at `$240`); 1,056.
|
|
820
|
+
- `tiles.ctm`: display `1`, method `0`, flags `$01`; 171 characters, 64 tiles of 4×4 (2,048 bytes), 64 names in 576 bytes, map 16×256 (8,192 bytes); 12,455.
|
|
821
|
+
- `introfont.ctm` and `statusfont.ctm` (identical): display `0`, method `0`, flags `$00`; 64 characters, no tiles, map 40×25 (2,000 bytes at `$25A`); 2,602.
|
|
822
|
+
- `scorefont.ctm`: display `1`, method `0`, flags `$00`; 16 characters, map 40×25; 2,170.
|
|
823
|
+
|
|
824
|
+
No sample has colouring method `1` or `2`, or display mode `3` or `4`, so rows 3 and 5 and the wider colour cells are Oscar64's reading and not measured here.
|
|
825
|
+
|
|
826
|
+
**Decoder.** The walker that produced the figures above, for a version 8 or 9 `.ctm` or a version 5 `.spd`. It prints each section's offset, marker and size and must end on the file's last byte; a mismatch means a section it does not know about.
|
|
827
|
+
|
|
828
|
+
```text
|
|
829
|
+
#!/usr/bin/env python3
|
|
830
|
+
# Walk a CharPad v8/v9 .ctm or a SpritePad v5 .spd and print each section's
|
|
831
|
+
# offset and size; the walk must end on the file's last byte.
|
|
832
|
+
# usage: ctm_walk.py FILE
|
|
833
|
+
import struct, sys
|
|
834
|
+
b = open(sys.argv[1], "rb").read()
|
|
835
|
+
u16 = lambda o: struct.unpack_from("<H", b, o)[0]
|
|
836
|
+
sig, ver = b[:3], b[3]
|
|
837
|
+
if sig == b"CTM" and ver in (8, 9):
|
|
838
|
+
disp, meth, flags = b[4], b[5], b[6]
|
|
839
|
+
p = 14 if ver == 8 else 19
|
|
840
|
+
per = {3: 2, 4: 3}.get(disp, 1) # colour bytes per cell
|
|
841
|
+
print(f"CTM v{ver} display={disp} method={meth} flags=${flags:02X}")
|
|
842
|
+
def section(name, size, hdr=0):
|
|
843
|
+
global p
|
|
844
|
+
print(f" ${p:04X} marker ${u16(p):04X} {name}: {size} bytes")
|
|
845
|
+
p += 2 + hdr + size
|
|
846
|
+
n = u16(p + 2) + 1
|
|
847
|
+
section("chars", 8 * n, 2)
|
|
848
|
+
section("materials", n)
|
|
849
|
+
if meth == 2: section("char colours", per * n)
|
|
850
|
+
t = 0
|
|
851
|
+
if flags & 1:
|
|
852
|
+
t, w, h = u16(p + 2) + 1, b[p + 4], b[p + 5]
|
|
853
|
+
section(f"tiles {t} of {w}x{h}", 2 * t * w * h, 4)
|
|
854
|
+
if meth == 1: section("tile colours", per * t)
|
|
855
|
+
section("tile tags", t)
|
|
856
|
+
q = p + 2
|
|
857
|
+
for _ in range(t):
|
|
858
|
+
q = b.index(0, q) + 1
|
|
859
|
+
section("tile names", q - p - 2)
|
|
860
|
+
mw, mh = u16(p + 2), u16(p + 4)
|
|
861
|
+
section(f"map {mw}x{mh}", 2 * mw * mh, 4)
|
|
862
|
+
elif sig == b"SPD" and ver == 5:
|
|
863
|
+
ns, nt, w, h = u16(5), u16(7), b[11], b[12]
|
|
864
|
+
print(f"SPD v5 sprites={ns} tiles={nt} colours={list(b[13:16])}")
|
|
865
|
+
p = 20 + 64 * ns + 2 * nt * w * h
|
|
866
|
+
print(f" sprites at $14, tiles at ${20 + 64 * ns:04X}, tables after ${p:04X}")
|
|
867
|
+
else:
|
|
868
|
+
sys.exit(f"not a CTM v8/v9 or SPD v5 file: {sig!r} version {ver}")
|
|
869
|
+
print(f" walk ends at {p}, file is {len(b)}: {'MATCH' if p == len(b) else 'trailing ' + str(len(b) - p)}")
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
On `background.ctm` it prints markers `$B0DA` to `$B5DA` at `$000E`, `$0152`, `$017C`, `$01D2`, `$01DE` and `$023A`, and `walk ends at 1056, file is 1056: MATCH`; on `sprites.spd`, `trailing 72`.
|
|
873
|
+
|
|
874
|
+
**What Oscar64 emits.** `ctm_chars` is the character section, 8 bytes a character. `ctm_attr1` is one byte a character: the material in the high nybble, and in colouring method `2` the character's colour in the low nybble; in colouring method `1` it is instead one byte a tile, the tile colour, with the materials discarded (read from Oscar64's reader, not measured here: no sample uses method `1`); `ctm_attr2` in display mode `4` packs the second and third colour bytes. `ctm_tiles8` and `ctm_map8` take the low byte of each 16-bit cell; `ctm_tiles16` and `ctm_map16` keep the word (declare the array `unsigned` and add the `word` specifier); `ctm_tiles8sw` swaps the array so the tile index is innermost. Measured on the windowless x64sc build of VICE 3.10 with Oscar64 1.32.271 embedding `background.ctm` and `mouse.spd`, the program printed, and Python read the same bytes from the same offsets: `CHARS 320: 00 00 00 FF 00 00 55 00`, `ATTR1 40: 00 20 10 30 40 60 50 70`, `TILES8 40: 00 01 02 03 04 05 06 07`, `TILES16 80: 0000 0001 0002 0003`, `MAP8 240: 06 06 06 06 06 06 06 06`, `SPRITES 1024: 00 00 00 F0 00 00 FC 00`. The exit screenshot is `../figures/ctm-spd-embed-probe.png`, identical bytes on two runs. No recipe page pins it: the verifier compiles a listing alone in a fresh directory, and an `#embed` needs the asset beside the source.
|
|
875
|
+
|
|
876
|
+
Oscar64's documentation names version 8; its reader also takes version 9, and it checks neither the signature nor any other version. Embedding the version 5 sample with `ctm_chars` compiled without a word of complaint and gave an array of 7,364 bytes: the reader took bytes `$04`–`$05` (`$0B00`) as the first marker, `$06`–`$07` plus one (3,085) as the character count, asked for 24,680 bytes and was handed the rest of the file, header, attributes, tiles and map together. Check the version byte before you embed.
|
|
877
|
+
|
|
878
|
+
---
|
|
879
|
+
|
|
880
|
+
### .SPD — SpritePad sprite set
|
|
881
|
+
|
|
882
|
+
**Consumed by:** oscar64
|
|
883
|
+
|
|
884
|
+
Oscar64 reads it through `#embed` with the specifiers `spd_sprites` and `spd_tiles`. The file begins with the three ASCII bytes `SPD` and a version byte, then a header whose length depends on the version, then the sprites as 64-byte blocks: 63 bytes of pixel data and one attribute byte. Oscar64's reader accepts versions 1, 3 and 5 and refuses a file without the signature.
|
|
885
|
+
|
|
886
|
+
**Header:**
|
|
887
|
+
|
|
888
|
+
| Offset (v1) | Offset (v5) | Size | Field |
|
|
889
|
+
|-------------|-------------|------|-------|
|
|
890
|
+
| $00–$02 | $00–$02 | 3 | Signature `SPD` |
|
|
891
|
+
| $03 | $03 | 1 | Version |
|
|
892
|
+
| – | $04 | 1 | Flags (samples: `$00`, `$02`; meaning not measured here) |
|
|
893
|
+
| $04 | – | 1 | Sprite count minus one (v1, not measured here) |
|
|
894
|
+
| $05 | – | 1 | Animation count minus one (v1, not measured here) |
|
|
895
|
+
| – | $05–$06 | 2 | Sprite count, little-endian, not minus one (samples: 16, 128) |
|
|
896
|
+
| – | $07–$08 | 2 | Tile count, little-endian (samples: 0, 0) |
|
|
897
|
+
| – | $09 | 1 | Sprite animation count (samples: 0, 11) |
|
|
898
|
+
| – | $0A | 1 | Tile animation count |
|
|
899
|
+
| – | $0B | 1 | Tile width in sprites |
|
|
900
|
+
| – | $0C | 1 | Tile height in sprites |
|
|
901
|
+
| $06–$08 | $0D–$0F | 3 | Transparent (background) colour, multicolour 1, multicolour 2 (v5 samples: `09 00 01`, `0E 00 01`; v1: Oscar64's reader, not measured here) |
|
|
902
|
+
| – | $10–$11 | 2 | Sprite overlay distance, signed little-endian (samples: 1) |
|
|
903
|
+
| – | $12–$13 | 2 | Tile overlay distance, signed little-endian (samples: 1) |
|
|
904
|
+
| header ends | $09 | $14 | | |
|
|
905
|
+
|
|
906
|
+
Version 3 is version 5 without the two overlay distances, a 16-byte header, from Oscar64's reader and not measured here. After the header come `64 × sprites` bytes, then `2 × tiles × width × height` bytes of little-endian sprite indices, then animation tables that this page does not decode: `mouse.spd` ends exactly after its 16 sprites (`$14` + 1,024 = 1,044), and `sprites.spd` has 72 bytes after its 128 sprites, which its 11 sprite animations account for in some layout not measured here.
|
|
907
|
+
|
|
908
|
+
The attribute byte at offset 63 of each block carries the sprite colour in bits 0–3, an overlay flag in bit 4 and the multicolour flag in bit 7 (Oscar64's reader comment; not measured here beyond the values seen: `$85` for 14 of the 16 mouse sprites, `$85`, `$87`, `$88` and `$8B` across the 128 game sprites, so bit 7 set and colours 5, 7, 8 and 11).
|
|
909
|
+
|
|
910
|
+
**A file with no signature.** The third sample starts `00 0B 01` and is 6,147 bytes: three bytes then 96 × 64. That is consistent with the older SpritePad's headerless save (three colour bytes, then the blocks), and the three values `00 0B 01` read as plausible colours; the producer is not established here, since nothing in the file names it. Such a file is distinguishable from the signed form only by the missing `SPD`. Oscar64 refuses it with "SPD file format not recognized"; a converter that keys on length can take it as `(size − 3) / 64` sprites.
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## Memory Snapshots
|
|
915
|
+
|
|
916
|
+
### .VSF — VICE snapshot
|
|
917
|
+
|
|
918
|
+
**Produced by:** vice
|
|
919
|
+
**Consumed by:** vice
|
|
920
|
+
|
|
921
|
+
A snapshot is VICE's own dump of the whole emulated machine: the 64 KiB of RAM, the CPU registers, and the state of every emulated chip, drive and port, one module each. VICE reads it back with `undump` or `-autostart` (see `../runtime/vice-reference.md`, Snapshots). ROM images are not stored; the file assumes the ROMs the emulator has loaded. The module layouts are per VICE version, so every offset below is what one file written by one build contained; a different version has to be decoded again with the script at the end of this section.
|
|
922
|
+
|
|
923
|
+
**Correction (2026-09-23).** This section used to describe a 15-byte module header with a 10-character name, and to list modules called `MEM`, `VICII` and `IEC`. None of that matched a file written by x64sc 3.10: the module header is 22 bytes with a 16-byte name, the memory module is `C64MEM`, the video module is `VIC-II`, and no `IEC` module was present. The tables below replace it, decoded from a file written and read for this page.
|
|
924
|
+
|
|
925
|
+
**Measured on:** the windowless x64sc 3.10 (`-default`, PAL), a 193,261-byte file written from the remote text monitor after a program had put known bytes in RAM and colour RAM. An NTSC run (`-model ntsc`, 179,053 bytes) gave the same header, the same module names and versions in the same order, and the same offsets inside `C64MEM` and `VIC-II`; only the `VIC-II` module's length differed, which moves every module after it.
|
|
926
|
+
|
|
927
|
+
**File header (58 bytes):**
|
|
928
|
+
|
|
929
|
+
| Offset | Size | Field | Value in this file |
|
|
930
|
+
|--------|------|-------|--------------------|
|
|
931
|
+
| $0000 | 19 | Magic string `VICE Snapshot File` followed by `$1A` | as named |
|
|
932
|
+
| $0013 | 1 | Snapshot format major version | 2 |
|
|
933
|
+
| $0014 | 1 | Snapshot format minor version | 0 |
|
|
934
|
+
| $0015 | 16 | Machine name, zero-padded | `C64SC` |
|
|
935
|
+
| $0025 | 13 | Version tag `VICE Version` followed by `$1A` | as named |
|
|
936
|
+
| $0032 | 4 | VICE version, one byte per component | 3, 10, 0, 0 |
|
|
937
|
+
| $0036 | 4 | Revision, little-endian | 0 |
|
|
938
|
+
|
|
939
|
+
**Module header (22 bytes, one per module):**
|
|
940
|
+
|
|
941
|
+
| Offset | Size | Field |
|
|
942
|
+
|--------|------|-------|
|
|
943
|
+
| +0 | 16 | Module name, zero-padded |
|
|
944
|
+
| +16 | 1 | Module major version |
|
|
945
|
+
| +17 | 1 | Module minor version |
|
|
946
|
+
| +18 | 4 | Module length, little-endian, counting this 22-byte header |
|
|
947
|
+
|
|
948
|
+
The length counts the header: adding each module's length to its own offset lands on the next module's name, and the last module ends at byte 193,261, the file's length.
|
|
949
|
+
|
|
950
|
+
**Modules in this file, in order (PAL run):**
|
|
951
|
+
|
|
952
|
+
| File offset | Name | Version | Length |
|
|
953
|
+
|-------------|------|---------|--------|
|
|
954
|
+
| 58 | `MAINCPU` | 1.4 | 125 |
|
|
955
|
+
| 183 | `C64MEM` | 0.1 | 65,577 |
|
|
956
|
+
| 65,760 | `C64CART` | 0.1 | 23 |
|
|
957
|
+
| 65,783 | `CIA1` | 2.5 | 99 |
|
|
958
|
+
| 65,882 | `CIA2` | 2.5 | 99 |
|
|
959
|
+
| 65,981 | `SID` | 1.5 | 58 |
|
|
960
|
+
| 66,039 | `SIDEXTENDED` | 1.4 | 155 |
|
|
961
|
+
| 66,194 | `DRIVE8` | 2.0 | 167 |
|
|
962
|
+
| 66,361 | `DRIVE9` | 2.0 | 167 |
|
|
963
|
+
| 66,528 | `DRIVE10` | 2.0 | 167 |
|
|
964
|
+
| 66,695 | `DRIVE11` | 2.0 | 167 |
|
|
965
|
+
| 66,862 | `DRIVECPU0` | 1.3 | 2,174 |
|
|
966
|
+
| 69,036 | `1541VIA1D0` | 2.2 | 50 |
|
|
967
|
+
| 69,086 | `VIA2D0` | 2.2 | 50 |
|
|
968
|
+
| 69,136 | `FSDRIVE` | 0.0 | 280 |
|
|
969
|
+
| 69,416 | `VIC-II` | 1.3 | 123,437 (NTSC: 109,229) |
|
|
970
|
+
| 192,853 | `GLUE` | 1.0 | 25 |
|
|
971
|
+
| 192,878 | `C64MEMHACKS` | 0.0 | 23 |
|
|
972
|
+
| 192,901 | `TAPEPORT` | 1.0 | 24 |
|
|
973
|
+
| 192,925 | `DATASETTE` | 1.5 | 100 |
|
|
974
|
+
| 193,025 | `KEYBOARD` | 1.1 | 118 |
|
|
975
|
+
| 193,143 | `JOYPORT0` | 0.0 | 23 |
|
|
976
|
+
| 193,166 | `JOYSTICK0` | 1.2 | 24 |
|
|
977
|
+
| 193,190 | `JOYPORT1` | 0.0 | 23 |
|
|
978
|
+
| 193,213 | `JOYSTICK1` | 1.2 | 24 |
|
|
979
|
+
| 193,237 | `USERPORT` | 1.0 | 24 |
|
|
980
|
+
|
|
981
|
+
No drive was attached in these runs, so the `DRIVE8` to `DRIVE11` modules are the 167-byte form and there is one `DRIVECPU0`; what a run with a true-drive 1541 attached adds was not measured here.
|
|
982
|
+
|
|
983
|
+
**`C64MEM` body (65,555 bytes after the header):**
|
|
984
|
+
|
|
985
|
+
| Body offset | Size | Field | Confirmed by |
|
|
986
|
+
|-------------|------|-------|--------------|
|
|
987
|
+
| +0 | 1 | Processor port data register (`$01`) | `$37`, the monitor's `01` column at the stop |
|
|
988
|
+
| +1 | 1 | Processor port direction register (`$00`) | `$2F`, the monitor's `00` column |
|
|
989
|
+
| +2 | 2 | Two bytes, both `$00` here | not decoded |
|
|
990
|
+
| +4 | 65,536 | RAM, `$0000` to `$FFFF` in address order | the 256 bytes `i XOR $A5` the program wrote at `$C000` sit at body offset 49,156, which is 4 + `$C000`; the BASIC stub is at 4 + `$0801` |
|
|
991
|
+
| +65,540 | 15 | Trailing bytes | not decoded |
|
|
992
|
+
|
|
993
|
+
RAM address `A` is therefore file byte 209 + `A` in this file (183 + 22 + 4). RAM bytes `$0000` and `$0001` read `$00 $00`: the port lives in the four bytes ahead of RAM, and reading it out of the RAM image gives the wrong answer.
|
|
994
|
+
|
|
995
|
+
**`VIC-II` body (123,415 bytes PAL, 109,207 NTSC):**
|
|
996
|
+
|
|
997
|
+
| Body offset | Size | Field | Confirmed by |
|
|
998
|
+
|-------------|------|-------|--------------|
|
|
999
|
+
| +0 | 1 | One byte: `$01` on the PAL run, `$03` on the NTSC run | meaning not established |
|
|
1000
|
+
| +1 | 64 | Register block, `$D000` to `$D03F`, holding the values last written | `$D020` at +33 read `$00` after the program wrote 0 to the border (power-on value `$0E`); `$D021` at +34 read `$06`; `$D018` at +25 read `$14` and `$D016` at +23 read `$08`, the written values, where a CPU read returns `$15` and `$C8` |
|
|
1001
|
+
| +65 | 696 | Internal state | not decoded |
|
|
1002
|
+
| +761 | 1,024 | Colour RAM, `$D800` to `$DBFF`, one byte per cell, low nybble | the 256 bytes `i AND $0F` the program wrote at `$D800` sit at +761; the cells from `$D900` to `$DBE7` read `$0E`, the KERNAL's text colour after the clear; the 24 cells past the screen's 1,000, `$DBE8` to `$DBFF`, are not touched by the clear and hold other values |
|
|
1003
|
+
| +1,785 | rest | Internal state; 14,208 bytes longer on PAL than on NTSC | not decoded |
|
|
1004
|
+
|
|
1005
|
+
**`MAINCPU` body (103 bytes):**
|
|
1006
|
+
|
|
1007
|
+
| Body offset | Size | Field | Confirmed by |
|
|
1008
|
+
|-------------|------|-------|--------------|
|
|
1009
|
+
| +0 | 8 | CPU clock, little-endian | 3,022,363, the monitor's `STOPWATCH` at the stop |
|
|
1010
|
+
| +8 | 1 | A | `$00` |
|
|
1011
|
+
| +9 | 1 | X | `$00` |
|
|
1012
|
+
| +10 | 1 | Y | `$00` |
|
|
1013
|
+
| +11 | 1 | SP | `$F6`, the monitor's `SP` |
|
|
1014
|
+
| +12 | 2 | PC, little-endian | `$0835`, the address the break was set on |
|
|
1015
|
+
| +14 | 1 | Status register | `$22`, the monitor's `..-...Z.` |
|
|
1016
|
+
| +15 | 88 | Rest of the module | not decoded |
|
|
1017
|
+
|
|
1018
|
+
**How the file was made.** This program clears the screen, prints a title, fills `$C000` to `$C0FF` with `i XOR $A5`, fills the first 256 colour cells with `i AND $0F`, sets the border to black and parks in a loop at `done` (`$0835`):
|
|
1019
|
+
|
|
1020
|
+
```kickass
|
|
1021
|
+
* = $0801
|
|
1022
|
+
.byte $0b, $08, $0a, $00, $9e, $32, $30, $36, $31, $00, $00, $00 // 10 SYS2061
|
|
1023
|
+
|
|
1024
|
+
* = $080d
|
|
1025
|
+
start:
|
|
1026
|
+
lda #$93 // clear screen
|
|
1027
|
+
jsr $ffd2
|
|
1028
|
+
ldx #$00
|
|
1029
|
+
print:
|
|
1030
|
+
lda msg,x
|
|
1031
|
+
beq fill
|
|
1032
|
+
jsr $ffd2
|
|
1033
|
+
inx
|
|
1034
|
+
bne print
|
|
1035
|
+
fill:
|
|
1036
|
+
ldx #$00
|
|
1037
|
+
loop:
|
|
1038
|
+
txa
|
|
1039
|
+
eor #$a5
|
|
1040
|
+
sta $c000,x
|
|
1041
|
+
txa
|
|
1042
|
+
and #$0f
|
|
1043
|
+
sta $d800,x
|
|
1044
|
+
inx
|
|
1045
|
+
bne loop
|
|
1046
|
+
lda #$00
|
|
1047
|
+
sta $d020
|
|
1048
|
+
done:
|
|
1049
|
+
jmp done
|
|
1050
|
+
|
|
1051
|
+
msg:
|
|
1052
|
+
.text "VSF PATTERN SET"
|
|
1053
|
+
.byte $00
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
A `-moncommands` file runs before the program does, so it cannot dump at once. It can arm a checkpoint whose attached command dumps when the program reaches a known store (`trace store d020` then `command 1 "dump \"file.vsf\""`; one process, and the last dump wins, so the KERNAL's own border write dumps first and the program's `STA $D020` overwrites it), or a remote-monitor client can stop the machine and dump. The second route is the one used below. `-initbreak 2101` is `$0835` in decimal. A Python socket polled the port from the moment x64sc was launched (it connected at 0.03 s and the stop arrived at 0.17 s), then sent the commands shown:
|
|
1057
|
+
|
|
1058
|
+
```text
|
|
1059
|
+
timeout 180 x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 \
|
|
1060
|
+
-limitcycles 8000000 -remotemonitor -remotemonitoraddress ip4://127.0.0.1:6577 \
|
|
1061
|
+
-initbreak 2101 -exitscreenshot vsf-pattern.png -autostart vsf-pattern.prg
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
```text
|
|
1065
|
+
#1 (Stop on exec 0835) 238/$0ee, 1/$01
|
|
1066
|
+
.C:0835 4C 35 08 JMP $0835 - A:00 X:00 Y:00 SP:f6 ..-...Z. 3022363
|
|
1067
|
+
(C:$0835) r
|
|
1068
|
+
ADDR A X Y SP 00 01 NV-BDIZC LIN CYC STOPWATCH
|
|
1069
|
+
.;0835 00 00 00 f6 2f 37 00100010 238 001 3022363
|
|
1070
|
+
(C:$0835) m c000 c00f
|
|
1071
|
+
>C:c000 a5 a4 a7 a6 a1 a0 a3 a2 ad ac af ae a9 a8 ab aa ..... ..........
|
|
1072
|
+
(C:$c010) m d800 d80f
|
|
1073
|
+
>C:d800 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f @abcdefghijklmno
|
|
1074
|
+
(C:$d810) dump "vsf-pattern.vsf"
|
|
1075
|
+
(C:$d810) del 1
|
|
1076
|
+
(C:$d810) x
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
`dump` prints nothing on success; the file appeared at once. Deleting the checkpoint before `x` matters: a break on a `JMP` to itself fires again on every iteration. The decoder that produced the tables above, run on the host against that file:
|
|
1080
|
+
|
|
1081
|
+
```text
|
|
1082
|
+
import struct, sys
|
|
1083
|
+
d = open(sys.argv[1], "rb").read()
|
|
1084
|
+
print(d[0:19], d[19], d[20], d[21:37].rstrip(b"\0"), d[37:50], list(d[50:54]), struct.unpack_from("<I", d, 54)[0])
|
|
1085
|
+
pos, mods = 58, {}
|
|
1086
|
+
while pos < len(d):
|
|
1087
|
+
name = d[pos:pos + 16].rstrip(b"\0").decode()
|
|
1088
|
+
size = struct.unpack_from("<I", d, pos + 18)[0]
|
|
1089
|
+
print(f"{pos:7d} {name:<12} {d[pos + 16]}.{d[pos + 17]:<2} {size:7d}")
|
|
1090
|
+
mods[name] = d[pos + 22:pos + size]
|
|
1091
|
+
pos += size
|
|
1092
|
+
mem, vic = mods["C64MEM"], mods["VIC-II"]
|
|
1093
|
+
ram = mem.find(bytes(i ^ 0xA5 for i in range(256))) - 0xC000
|
|
1094
|
+
print("port bytes", mem[:ram].hex(" "), "| RAM at body+%d, trailing %d" % (ram, len(mem) - ram - 65536))
|
|
1095
|
+
col = vic.find(bytes(i & 0x0F for i in range(256)))
|
|
1096
|
+
print("colour RAM at body+%d; $D020 at body+%d = %02x" % (col, 1 + 0x20, vic[1 + 0x20]))
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
Its last two lines for the PAL file were `port bytes 37 2f 00 00 | RAM at body+4, trailing 15` and `colour RAM at body+761; $D020 at body+33 = 00`.
|
|
1100
|
+
|
|
1101
|
+
**What a snapshot is good for in a headless pipeline.** Two things. First, a state to diff: after a run, the RAM image at file byte 209 is the whole address space in order, so a test can compare the bytes a program owns against an expected image, or two runs against each other, without printing anything to the screen. Do not expect two snapshots of the same program to be byte-identical: the PAL run above, repeated, gave a file that differed in 946 bytes, 943 of them single bytes scattered through RAM at addresses the program never wrote (the emulated power-on contents) and 3 in the CIA modules, while the CPU clock was the same 3,022,363 in both. Diff the regions the program wrote, the register block and colour RAM, not the whole file. Second, a save point: a long run can be stopped once at a known address, dumped, and every later test can start from that file with `undump "file.vsf"` in a `-moncommands` file, or by passing it to `-autostart`, which skips the boot and the load each time. The VICE reference's Snapshots section has the restore side.
|
|
1102
|
+
|
|
1103
|
+
VSF is strictly a VICE internal format. It is not suitable for interchange between emulators and has no use in the toolchain build pipeline.
|
|
1104
|
+
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
## Build Artifacts
|
|
1108
|
+
|
|
1109
|
+
### .MAP — Linker map file
|
|
1110
|
+
|
|
1111
|
+
**Produced by:** oscar64, cc65
|
|
1112
|
+
**Consumed by:** vice
|
|
1113
|
+
|
|
1114
|
+
A linker map file records the final address assignments for every symbol, segment, and object file resolved during linking. Oscar64 emits `.map` alongside its primary output. cc65's `ld65` linker emits a map file when invoked with `-m`. KickAssembler produces a symbol list via the `--symboldump` flag (see `.VS` below) rather than a traditional map file.
|
|
1115
|
+
|
|
1116
|
+
Map files are consumed by VICE's monitor for symbol-name display during debugging: the monitor's `ll` command (load labels) accepts Oscar64's `.lbl` format; `.map` files require conversion or manual parsing.
|
|
1117
|
+
|
|
1118
|
+
Format is tool-specific text: Oscar64 emits one `symbol = $ADDR` line per resolved symbol. cc65 `ld65` emits a structured text report with segment summary, module summary, and symbol table sections.
|
|
1119
|
+
|
|
1120
|
+
---
|
|
1121
|
+
|
|
1122
|
+
### .LBL — VICE symbol file (Oscar64 native output)
|
|
1123
|
+
|
|
1124
|
+
**Produced by:** oscar64
|
|
1125
|
+
**Consumed by:** vice
|
|
1126
|
+
|
|
1127
|
+
Oscar64 natively emits `.lbl` files containing symbol-to-address mappings in VICE monitor label format. Each line is:
|
|
1128
|
+
|
|
1129
|
+
```
|
|
1130
|
+
al HHHH .SYMBOLNAME
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
where `HHHH` is the 4-digit hex address and `SYMBOLNAME` is the C or assembly label. The leading `al` prefix is the VICE monitor `add_label` command mnemonic.
|
|
1134
|
+
|
|
1135
|
+
VICE loads `.lbl` files via `ll <filename>` in its built-in monitor, enabling symbolic display of disassembly, breakpoints by name (`break main`), and watch expressions. This tight integration between Oscar64 and VICE is a primary reason for Oscar64's status as the preferred toolchain in this KB.
|
|
1136
|
+
|
|
1137
|
+
---
|
|
1138
|
+
|
|
1139
|
+
### .VS — KickAssembler/VICE symbol file
|
|
1140
|
+
|
|
1141
|
+
**Produced by:** kickassembler
|
|
1142
|
+
**Consumed by:** vice
|
|
1143
|
+
|
|
1144
|
+
KickAssembler emits a VICE symbol file (conventionally `.vs` or `-symbols.txt`) when invoked with the `--vicesymbols` flag. Format matches the VICE `al` label format used by `.LBL` files. The file is loaded into VICE the same way (`ll <filename>`) and provides identical symbolic debugging capability to Oscar64's `.lbl` output.
|
|
1145
|
+
|
|
1146
|
+
---
|
|
1147
|
+
|
|
1148
|
+
### .ASM — Generated assembly listing
|
|
1149
|
+
|
|
1150
|
+
**Produced by:** oscar64
|
|
1151
|
+
**Consumed by:** kickassembler, cc65
|
|
1152
|
+
|
|
1153
|
+
Oscar64 can emit an assembly listing (`.asm`) showing the 6502 instructions generated for each C source statement. This intermediate representation is useful for cycle-counting and verifying that the compiler has produced efficient code for hot paths (raster handlers, sprite sorters, etc.).
|
|
1154
|
+
|
|
1155
|
+
The listing format is not a formal standard; it is Oscar64-specific human-readable text with C source lines interleaved with the generated opcodes and addresses. It is not directly assembled by KickAssembler or cc65 — the "Consumed by" relationship above refers to the practice of manually extracting hot inner loops for hand-optimization in an assembler.
|
|
1156
|
+
|
|
1157
|
+
---
|
|
1158
|
+
|
|
1159
|
+
### .S — cc65/ca65 assembly source
|
|
1160
|
+
|
|
1161
|
+
**Produced by:** oscar64, cc65
|
|
1162
|
+
**Consumed by:** cc65
|
|
1163
|
+
|
|
1164
|
+
The `.s` extension is the standard assembly source file for ca65 (the assembler component of the cc65 suite). cc65 (the C compiler) emits `.s` files as its intermediate representation before invoking ca65. The ca65 assembler then compiles `.s` to `.o` object files.
|
|
1165
|
+
|
|
1166
|
+
Oscar64 can also emit `.s` compatible output in some configurations, but its primary output path does not route through ca65. The `.s` extension is also used generically for 6502 assembly in other contexts (e.g., manual assembly sources targeting ca65 directly).
|
|
1167
|
+
|
|
1168
|
+
---
|
|
1169
|
+
|
|
1170
|
+
### .O — ca65 object file
|
|
1171
|
+
|
|
1172
|
+
**Produced by:** cc65
|
|
1173
|
+
**Consumed by:** cc65
|
|
1174
|
+
|
|
1175
|
+
The `.o` (or `.obj`) file is the output of ca65 after assembling a `.s` source file. Object files are intermediate binary artifacts containing relocatable code and unresolved symbol references. The `ld65` linker consumes one or more `.o` files (plus a linker config file and the cc65 runtime library) to produce a final `.prg`, `.bin`, or other target format.
|
|
1176
|
+
|
|
1177
|
+
Object file format is ca65-specific binary and not directly human-readable. It is not used by Oscar64 or KickAssembler, which have their own internal object representations.
|
|
1178
|
+
|
|
1179
|
+
---
|
|
1180
|
+
|
|
1181
|
+
## Source Archives
|
|
1182
|
+
|
|
1183
|
+
### .P00 — PC64 P00 archive (single-file PRG container)
|
|
1184
|
+
|
|
1185
|
+
**Consumed by:** vice
|
|
1186
|
+
|
|
1187
|
+
The P00 format (from the PC64 emulator by Wolfgang Lorenz) wraps a single C64 PRG file in a 26-byte header that preserves the original PETASCII filename and file type — information that is lost when storing a C64 file on a host filesystem that does not support PETASCII or Commodore file-type metadata.
|
|
1188
|
+
|
|
1189
|
+
**File header (26 bytes):**
|
|
1190
|
+
|
|
1191
|
+
| Offset | Size | Field |
|
|
1192
|
+
|--------|------|-------|
|
|
1193
|
+
| $00–$06 | 7 | Magic string `"C64File"` |
|
|
1194
|
+
| $07 | 1 | Null terminator (`$00`) |
|
|
1195
|
+
| $08–$17 | 16 | PETASCII filename (null-padded, NOT `$A0`-padded as on disk) |
|
|
1196
|
+
| $18 | 1 | Null (`$00`) |
|
|
1197
|
+
| $19 | 1 | REL file record size (`$00` if not a REL file) |
|
|
1198
|
+
| $1A+ | var | PRG data (load address + program bytes) |
|
|
1199
|
+
|
|
1200
|
+
The file extension encodes the C64 file type: `.P00` = PRG, `.S00` = SEQ, `.U00` = USR, `.R00` = REL. When multiple C64 files have names that map to the same host DOS name, the numeric suffix increments (`.P00`, `.P01`, `.P02`, ...).
|
|
1201
|
+
|
|
1202
|
+
P00 is a legacy format predating modern emulators' ability to handle PETASCII transparently. VICE supports it for loading archived software collections assembled in the PC64 era. It is not produced by any toolchain in this KB.
|
|
1203
|
+
|
|
1204
|
+
---
|
|
1205
|
+
|
|
1206
|
+
## See also
|
|
1207
|
+
|
|
1208
|
+
- `../hardware/kernal-routines-reference.md` — LOAD, SAVE, OPEN, CLOSE, CHKIN, CHKOUT
|
|
1209
|
+
- `iec-disk-reference.md` — IEC bus protocol and 1541 drive internals
|
|
1210
|
+
- `../runtime/vice-reference.md` — VICE emulator usage and debugging
|
|
1211
|
+
- `../hardware/c64-memory-map.md` — C64 memory map (tape buffer at `$033C`, disk buffer at `$0200`)
|
|
1212
|
+
- `../art/asset-pipelines.md` — getting CharPad and SpritePad output into a build
|