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,1686 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: banking
|
|
3
|
+
chip: 6510
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- doc-type: technique-reference -->
|
|
7
|
+
|
|
8
|
+
# Memory Banking Techniques
|
|
9
|
+
|
|
10
|
+
The Commodore 64 is a machine built on layered illusions. Its 6510 CPU sees a flat
|
|
11
|
+
16-bit address space, but that 64 KB window simultaneously looks onto 64 KB of
|
|
12
|
+
dynamic RAM, 8 KB of BASIC ROM, 8 KB of KERNAL ROM, 4 KB of character generator
|
|
13
|
+
ROM, the VIC-II register set, the SID, two CIA chips, and a slab of Color RAM.
|
|
14
|
+
None of those components occupy distinct address ranges — they all occupy the same
|
|
15
|
+
ranges, bank-switched in and out by hardware. The programmer's job is to control
|
|
16
|
+
which physical device answers a given address at any given moment, and to keep
|
|
17
|
+
the VIC-II's separate 16 KB view coherent with whatever CPU-visible layout has
|
|
18
|
+
been selected.
|
|
19
|
+
|
|
20
|
+
Banking on the C64 is not a special mode you enable for advanced work. It is the
|
|
21
|
+
default state of the machine from the first instruction after reset. Every write to
|
|
22
|
+
$D011 silently depends on the I/O chip being visible at $D000. Every IRQ relies on
|
|
23
|
+
KERNAL ROM or a RAM replacement being readable at $FFFA-$FFFF. Understanding the
|
|
24
|
+
banking system is a prerequisite for any serious C64 development.
|
|
25
|
+
|
|
26
|
+
This document covers the CPU I/O port that selects which ROMs are visible, the
|
|
27
|
+
CIA2 register that defines VIC's 16 KB working window, the relationship between
|
|
28
|
+
VIC banking and character ROM visibility, $D018's role in positioning screen RAM
|
|
29
|
+
and bitmaps within the VIC bank, the EasyFlash cartridge's per-bank mechanism,
|
|
30
|
+
and the technique of hiding working data under KERNAL ROM.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## cpu_io_port_bank — $00/$01 port banking
|
|
35
|
+
|
|
36
|
+
**Complexity:** medium
|
|
37
|
+
**Region:** both
|
|
38
|
+
|
|
39
|
+
### Why
|
|
40
|
+
|
|
41
|
+
Every C64 program eventually outgrows the default memory layout. BASIC ROM at
|
|
42
|
+
$A000-$BFFF eats 8 KB that could hold graphics data, music, or game code.
|
|
43
|
+
KERNAL ROM at $E000-$FFFF eats another 8 KB. If a program needs to address the
|
|
44
|
+
character ROM to copy its bitmap patterns into RAM, the I/O chips must be
|
|
45
|
+
temporarily replaced by the character ROM window. The CPU I/O port is the
|
|
46
|
+
single switch that controls all of this.
|
|
47
|
+
|
|
48
|
+
### How
|
|
49
|
+
|
|
50
|
+
The 6510 CPU has six on-chip general-purpose I/O pins that are accessible
|
|
51
|
+
through two memory locations that exist below the normal RAM map:
|
|
52
|
+
|
|
53
|
+
- **$0000 — Data Direction Register (DDR).** Each bit set to 1 makes the
|
|
54
|
+
corresponding $0001 bit an output; each bit set to 0 makes it an input.
|
|
55
|
+
The DDR must be configured before writing $0001 has any effect on output pins.
|
|
56
|
+
The KERNAL sets the DDR to $2F at reset (bits 0-3 and 5 are outputs; bit 4,
|
|
57
|
+
the cassette sense line, is an input).
|
|
58
|
+
|
|
59
|
+
- **$0001 — Data register.** Bits 0-2 are the three banking control lines.
|
|
60
|
+
Writing a new value here changes which ROMs the PLA presents to the CPU.
|
|
61
|
+
|
|
62
|
+
The three relevant bits are:
|
|
63
|
+
|
|
64
|
+
| Bit | Name | 0 means | 1 means |
|
|
65
|
+
|-----|--------|--------------------------|--------------------------------------|
|
|
66
|
+
| 0 | LORAM | $A000-$BFFF is RAM | $A000-$BFFF can be BASIC ROM |
|
|
67
|
+
| 1 | HIRAM | $E000-$FFFF is RAM | $E000-$FFFF can be KERNAL ROM |
|
|
68
|
+
| 2 | CHAREN | $D000-$DFFF is char ROM | $D000-$DFFF is I/O (VIC/SID/CIA) |
|
|
69
|
+
|
|
70
|
+
BASIC ROM appears only when both LORAM and HIRAM are 1. KERNAL ROM appears
|
|
71
|
+
when HIRAM is 1 (regardless of LORAM). Character ROM appears at $D000-$DFFF
|
|
72
|
+
when CHAREN is 0 and at least one of LORAM or HIRAM is 1.
|
|
73
|
+
|
|
74
|
+
The commonly-used configurations are:
|
|
75
|
+
|
|
76
|
+
| $01 value | Name | $A000-$BFFF | $D000-$DFFF | $E000-$FFFF |
|
|
77
|
+
|-----------|-------------------------------|-------------|-------------|-------------|
|
|
78
|
+
| $37 (55) | Default (BASIC + KERNAL + I/O)| BASIC ROM | I/O | KERNAL ROM |
|
|
79
|
+
| $36 (54) | KERNAL + I/O (no BASIC) | RAM | I/O | KERNAL ROM |
|
|
80
|
+
| $35 (53) | I/O only (no ROM) | RAM | I/O | RAM |
|
|
81
|
+
| $34 (52) | All RAM (no ROM, no I/O) | RAM | RAM | RAM |
|
|
82
|
+
| $33 (51) | BASIC + KERNAL + char ROM | BASIC ROM | Char ROM | KERNAL ROM |
|
|
83
|
+
|
|
84
|
+
Mode $35 is the most common alternative to the default for demos and games: all
|
|
85
|
+
ROM is banked out, I/O remains accessible at $D000-$DFFF so the program can still
|
|
86
|
+
talk to VIC-II, SID, and CIA. Mode $34 replaces I/O with RAM as well, giving a
|
|
87
|
+
flat 64 KB of RAM at the cost of losing direct register access.
|
|
88
|
+
|
|
89
|
+
Mode $33 is used when a program needs to read the built-in character ROM at
|
|
90
|
+
$D000-$DFFF — for example to copy the ROM font into a custom RAM location for
|
|
91
|
+
modification.
|
|
92
|
+
|
|
93
|
+
### Why it works
|
|
94
|
+
|
|
95
|
+
The PLA chip (906114) sits between the CPU's address and data buses and all the
|
|
96
|
+
peripheral devices. It monitors the three processor-port bits from the 6510 along
|
|
97
|
+
with the GAME and EXROM lines from the cartridge port. For any given combination
|
|
98
|
+
of those five inputs it asserts or deasserts the chip-select lines for each ROM
|
|
99
|
+
and the I/O devices. The RAM is always physically present; the PLA merely disables
|
|
100
|
+
the RAM's CAS line in a specific address range when it wants a ROM or I/O device
|
|
101
|
+
to win that range on reads. Writes to a ROM-mapped range always go to the
|
|
102
|
+
underlying RAM — the PLA routes a write cycle at $A000-$BFFF or $E000-$FFFF to
|
|
103
|
+
RAM whatever $01 says, and the same holds for $D000-$DFFF while character ROM is
|
|
104
|
+
mapped there (CHAREN = 0 with LORAM or HIRAM set). It is NOT true of $D000-$DFFF
|
|
105
|
+
while I/O is mapped ($35/$36/$37): there the write goes to the VIC/SID/CIA/colour-RAM
|
|
106
|
+
register and the RAM beneath is untouched (measured in VICE x64sc: RAM under $D000
|
|
107
|
+
seeded $1D in mode $34; $2D written to $D000 with $01 = $37 read back as $2D — the
|
|
108
|
+
sprite-0 X register — and the RAM under it still read $1D once I/O was banked out;
|
|
109
|
+
the same test with $E000 put the byte in RAM). An earlier version of this page
|
|
110
|
+
said writes go to RAM in every banking state. So you can write code or data into
|
|
111
|
+
$E000-$FFFF even while KERNAL ROM is banked in — the bytes land in RAM and become
|
|
112
|
+
visible once KERNAL is banked out; to put data under I/O, bank it out first ($34,
|
|
113
|
+
or $33 if char ROM is acceptable) with interrupts disabled.
|
|
114
|
+
|
|
115
|
+
### Variations
|
|
116
|
+
|
|
117
|
+
**Mode $35 with custom IRQ vectors.** When HIRAM goes to 0, the CPU's hardware
|
|
118
|
+
IRQ vector at $FFFE/$FFFF and NMI vector at $FFFA/$FFFB are no longer in ROM —
|
|
119
|
+
they read from RAM. Before switching to $35, disable interrupts with SEI, write
|
|
120
|
+
your IRQ and NMI handler addresses to the RAM at $FFFE/$FFFB (these are in the
|
|
121
|
+
underlying RAM even while KERNAL ROM covers them — just write directly), then
|
|
122
|
+
write $35 to $01 and re-enable with CLI. The KERNAL-provided interrupt chain at
|
|
123
|
+
$EA31 is gone; the program owns all interrupts.
|
|
124
|
+
|
|
125
|
+
**Temporarily banking in char ROM.** To copy the ROM font to a custom location:
|
|
126
|
+
disable IRQs (SEI), write $33 to $01 to swap char ROM into $D000-$DFFF, perform
|
|
127
|
+
the copy loop, then write $37 back to restore normal layout, then CLI. The copy
|
|
128
|
+
must be complete before re-enabling the I/O chips.
|
|
129
|
+
|
|
130
|
+
**Preserving bits 3-5.** Bits 3 and 5 of $01 are datasette outputs — bit 3 the
|
|
131
|
+
write line, bit 5 the motor (0 = motor ON) — and bit 4 is the tape-button sense
|
|
132
|
+
input (DDR $2F leaves it an input, so writes to it do nothing). The constants
|
|
133
|
+
above ($33-$37) all keep bit 5 set, so writing them directly does not start the
|
|
134
|
+
motor; a value with bit 5 clear does — `LDA #$05 / STA $01` banks out the ROMs
|
|
135
|
+
and switches the motor on, and under mode $35 nothing turns it off again, because
|
|
136
|
+
the interlock that does so ($EA61) is part of the KERNAL IRQ you have just
|
|
137
|
+
removed. Prefer read-modify-write when changing only the banking bits so that
|
|
138
|
+
bits 3 and 5 are left as the tape code set them: read $01, AND #$F8, OR in the
|
|
139
|
+
new banking value, write back. (An earlier version of this paragraph called bit 4
|
|
140
|
+
an output that controls the datasette; it is the switch-sense input, as the DDR
|
|
141
|
+
note above says.)
|
|
142
|
+
|
|
143
|
+
### Cycle budget
|
|
144
|
+
|
|
145
|
+
Banking switches take effect on the very next bus cycle after the write to $01
|
|
146
|
+
completes. There is no pipeline delay or setup time. The PLA combinatorially
|
|
147
|
+
decodes the processor-port lines on every cycle, so a STA $01 is followed
|
|
148
|
+
immediately by the new memory layout being visible on the next instruction fetch.
|
|
149
|
+
|
|
150
|
+
### Recipes
|
|
151
|
+
|
|
152
|
+
No Phase 3 recipes target this technique directly. Banking is a supporting
|
|
153
|
+
infrastructure technique — recipes for specific effects (stable IRQ, raster bars,
|
|
154
|
+
custom charsets) use mode $35 or $37 as their starting point. Recipes land in
|
|
155
|
+
Phase 4+.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## vic_bank_select — CIA2 VIC bank select
|
|
160
|
+
|
|
161
|
+
**Complexity:** low
|
|
162
|
+
**Region:** both
|
|
163
|
+
**Uses registers:** DD00
|
|
164
|
+
**Claims:** cia2_vic_bank (owns)
|
|
165
|
+
**Claims basis:** estimated
|
|
166
|
+
|
|
167
|
+
### Why
|
|
168
|
+
|
|
169
|
+
The VIC-II chip has a 14-bit internal address bus: it can independently access
|
|
170
|
+
any of 16,384 bytes (16 KB) of the C64's RAM. But the C64 has 64 KB of RAM.
|
|
171
|
+
To allow VIC to reach graphics data anywhere in the address space, CIA2 port A
|
|
172
|
+
bits 0-1 act as a two-bit extension that shifts VIC's 16 KB window to one of
|
|
173
|
+
four positions. Choosing the right VIC bank is the first decision in any
|
|
174
|
+
memory layout — everything else (screen RAM position, char set or bitmap position,
|
|
175
|
+
sprite data, sprite pointer table) is relative to whichever 16 KB bank VIC sees.
|
|
176
|
+
|
|
177
|
+
### How
|
|
178
|
+
|
|
179
|
+
CIA2 port A, the register at $DD00, multiplexes the VIC bank selection onto its
|
|
180
|
+
lowest two bits alongside the IEC serial bus and RS-232 lines:
|
|
181
|
+
|
|
182
|
+
| $DD00 bits 1-0 | VIC bank | Address range in CPU view |
|
|
183
|
+
|----------------|----------|---------------------------|
|
|
184
|
+
| 11 | 0 | $0000-$3FFF |
|
|
185
|
+
| 10 | 1 | $4000-$7FFF |
|
|
186
|
+
| 01 | 2 | $8000-$BFFF |
|
|
187
|
+
| 00 | 3 | $C000-$FFFF |
|
|
188
|
+
|
|
189
|
+
After reset the KERNAL sets $DD02 (CIA2 port A DDR) to $3F (bits 0-5 are
|
|
190
|
+
outputs). Bits 0-1 of $DD00 reset to 11, placing VIC in bank 0 — which is why
|
|
191
|
+
the default screen at $0400 is visible to VIC without any setup.
|
|
192
|
+
|
|
193
|
+
To change the bank, always read-modify-write $DD00: read the current value, mask
|
|
194
|
+
off bits 0-1 with AND #$FC, then OR in the desired two-bit bank code, then write
|
|
195
|
+
back. This preserves the IEC bus state and prevents glitches on the serial port.
|
|
196
|
+
|
|
197
|
+
### Why it works
|
|
198
|
+
|
|
199
|
+
Between CIA2 port A pins PA0-PA1 and the VIC-II's address-bus extension inputs
|
|
200
|
+
VA14-VA15 there is a pair of inverting buffers on the C64 motherboard. The two
|
|
201
|
+
bit-patterns are therefore inverted with respect to each other: when CIA2 drives
|
|
202
|
+
both pins high (the all-ones reset state), the VIC receives low on VA14-VA15,
|
|
203
|
+
which it interprets as bank 0. The counter-intuitive mapping — that bit pattern
|
|
204
|
+
11 means bank 0 — comes from this inversion. The design choice is pragmatic:
|
|
205
|
+
TTL open-collector bus lines default to a pulled-high state, so the safe reset
|
|
206
|
+
state (everything high) naturally delivers VIC bank 0 and screen RAM at $0400
|
|
207
|
+
without any software configuration.
|
|
208
|
+
|
|
209
|
+
After changing $DD00, the new bank address extension takes effect immediately on
|
|
210
|
+
the next VIC fetch cycle. If the raster is currently rendering and VIC is
|
|
211
|
+
mid-fetch, the visible result will change partway through the current line.
|
|
212
|
+
Production code always makes bank changes during the vertical blank or in a
|
|
213
|
+
raster interrupt timed to occur in the overscan region.
|
|
214
|
+
|
|
215
|
+
### Variations
|
|
216
|
+
|
|
217
|
+
**Banks 1 and 3 for full custom layouts.** VIC banks 1 ($4000-$7FFF) and 3
|
|
218
|
+
($C000-$FFFF) contain no char ROM shadow; the VIC sees only RAM there. This is
|
|
219
|
+
the preferred layout for demos that use fully custom graphics and want maximum
|
|
220
|
+
clarity: all graphics live in one 16 KB bank, the CPU never accidentally reads
|
|
221
|
+
char ROM data, and the mapping is unambiguous.
|
|
222
|
+
|
|
223
|
+
**Multi-bank sprite tricks.** Sprite data must be addressable within VIC's current
|
|
224
|
+
bank, but the CPU can freely write into any bank's RAM while VIC sees only its
|
|
225
|
+
own bank. Double-buffering sprite data in the off-bank (writing to the other
|
|
226
|
+
16 KB while VIC reads this 16 KB) is a clean way to animate sprites without
|
|
227
|
+
tearing, by flipping $DD00 once per frame rather than per-sprite.
|
|
228
|
+
|
|
229
|
+
### Cycle budget
|
|
230
|
+
|
|
231
|
+
No critical cycle constraints. The $DD00 write is a single STA instruction;
|
|
232
|
+
the bank switch is instantaneous from VIC's perspective on the next cycle.
|
|
233
|
+
|
|
234
|
+
### Recipes
|
|
235
|
+
|
|
236
|
+
No Phase 3 recipes. VIC bank selection is a layout prerequisite used by all
|
|
237
|
+
graphics recipes. Recipes land in Phase 4+.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## char_rom_under_vic — Map char ROM into VIC's bank
|
|
242
|
+
|
|
243
|
+
**Complexity:** medium
|
|
244
|
+
**Region:** both
|
|
245
|
+
**Uses registers:** DD00, D018
|
|
246
|
+
|
|
247
|
+
### Why
|
|
248
|
+
|
|
249
|
+
The C64 ships with a 4 KB character generator ROM containing the default
|
|
250
|
+
uppercase/graphics and lower-case/uppercase font data. Most programs need a
|
|
251
|
+
custom font, custom symbols, or a modified character set at some point. Before
|
|
252
|
+
any custom character data can be used, the developer must either place their
|
|
253
|
+
custom data at the address VIC expects to find characters, or understand how
|
|
254
|
+
the ROM characters are shadowed into VIC banks 0 and 2 so that the default
|
|
255
|
+
font remains available without consuming RAM.
|
|
256
|
+
|
|
257
|
+
### How
|
|
258
|
+
|
|
259
|
+
The character ROM resides at $D000-$DFFF from the CPU's perspective (when CHAREN
|
|
260
|
+
is 0 and at least LORAM or HIRAM is 1). But VIC-II does not share the CPU's
|
|
261
|
+
address space — VIC has its own 16 KB window determined by $DD00. The character
|
|
262
|
+
ROM hardware includes a second set of decode logic that makes it appear inside
|
|
263
|
+
VIC banks 0 and 2 at specific offsets:
|
|
264
|
+
|
|
265
|
+
| VIC bank | CPU range | Char ROM visible to VIC at | VIC-relative address |
|
|
266
|
+
|----------|---------------|---------------------------|----------------------|
|
|
267
|
+
| 0 | $0000-$3FFF | Yes | $1000-$1FFF (CPU $1000-$1FFF) |
|
|
268
|
+
| 1 | $4000-$7FFF | No — VIC sees RAM only | n/a |
|
|
269
|
+
| 2 | $8000-$BFFF | Yes | $1000-$1FFF (CPU $9000-$9FFF) |
|
|
270
|
+
| 3 | $C000-$FFFF | No — VIC sees RAM only | n/a |
|
|
271
|
+
|
|
272
|
+
In VIC bank 0, addresses $1000-$1FFF in VIC's view return char ROM data even
|
|
273
|
+
though those CPU addresses ($1000-$1FFF) contain normal RAM. The KERNAL defaults
|
|
274
|
+
take advantage of this: the default $D018 value of $14 points the character
|
|
275
|
+
generator base to $1000 within bank 0, which silently reads the char ROM without
|
|
276
|
+
occupying any RAM.
|
|
277
|
+
|
|
278
|
+
To place a custom character set at a specific address, choose a VIC bank and
|
|
279
|
+
pick a character base address that does not conflict with the char ROM shadow.
|
|
280
|
+
Common choices:
|
|
281
|
+
|
|
282
|
+
- Bank 0, char base at $2000 (VIC-relative): no char ROM interference.
|
|
283
|
+
CPU loads the charset into RAM at $2000-$27FF.
|
|
284
|
+
$D018 CB field = %100 (= 4), giving $D018 = $18 for VM at $0400 + CB at $2000.
|
|
285
|
+
(An earlier version said $14; $14 is CB = %010 = $1000, the char ROM shadow,
|
|
286
|
+
i.e. the KERNAL default.)
|
|
287
|
+
|
|
288
|
+
- Bank 1 (any address): no char ROM shadow in bank 1 at all.
|
|
289
|
+
CPU loads the charset anywhere in $4000-$7FFF.
|
|
290
|
+
|
|
291
|
+
- Bank 2, char base at VIC-relative $0000 (CPU $8000), $D018 CB field = %000:
|
|
292
|
+
clear of the char ROM shadow, which in bank 2 sits at VIC-relative $1000-$1FFF
|
|
293
|
+
(CPU $9000-$9FFF). CPU loads the charset into RAM at $8000-$87FF. (An earlier
|
|
294
|
+
version of this bullet and of the table above put CPU addresses in the
|
|
295
|
+
VIC-relative slots for bank 2; VIC-relative addresses only run $0000-$3FFF.
|
|
296
|
+
Measured in VICE x64sc: bank 2, CB=0 reads CPU $8000; bank 2, CB=2 reads char
|
|
297
|
+
ROM although CPU $9000 RAM is zero.)
|
|
298
|
+
|
|
299
|
+
### Why it works
|
|
300
|
+
|
|
301
|
+
Inside the C64 motherboard, the character ROM's chip-select line responds not
|
|
302
|
+
only to the CPU-side address decoder but also to a separate signal derived from
|
|
303
|
+
VIC's address bus via the same PLA. When VIC is in bank 0 or bank 2, and VIC
|
|
304
|
+
generates an internal address in the $1000-$1FFF window — CPU $1000-$1FFF in
|
|
305
|
+
bank 0, CPU $9000-$9FFF in bank 2 — the PLA asserts char ROM's chip-select instead of RAM's CAS
|
|
306
|
+
line. The CPU never knows this is happening — from the CPU's side those are
|
|
307
|
+
ordinary RAM locations whose content is readable and writable. The char ROM
|
|
308
|
+
shadow is VIC-only hardware behavior, not a side-effect of CPU banking.
|
|
309
|
+
|
|
310
|
+
### Variations
|
|
311
|
+
|
|
312
|
+
**Dual-charset switching.** Because custom charsets live in RAM and the char
|
|
313
|
+
ROM shadow is a hardware overlay, a program can maintain two charsets in RAM
|
|
314
|
+
at different $D018 CB offsets and switch fonts by writing $D018 on a per-raster-
|
|
315
|
+
line basis in an IRQ. The VIC applies the new $D018 value from the next character
|
|
316
|
+
fetch onward, enabling per-line font changes.
|
|
317
|
+
|
|
318
|
+
**Charset placement at $3800 in bank 0.** Advanced demos sometimes place a
|
|
319
|
+
charset near the end of bank 0 ($3800-$3FFF is 2 KB). This allows sprite pointers
|
|
320
|
+
and the sprite pool to coexist in the same 16 KB bank with no gaps.
|
|
321
|
+
|
|
322
|
+
**Copying the ROM font.** To modify the built-in font, copy it first: disable
|
|
323
|
+
IRQs, set $01 to $33 (char ROM in $D000-$DFFF), copy 2 KB from $D000-$D7FF
|
|
324
|
+
into target RAM, restore $01 to $37. Then point $D018 at the RAM copy.
|
|
325
|
+
|
|
326
|
+
### Cycle budget
|
|
327
|
+
|
|
328
|
+
No cycle budget for the bank configuration itself. If $D018 is changed inside a
|
|
329
|
+
raster IRQ, the timing of where the new char base takes effect matters — see the
|
|
330
|
+
raster techniques doc.
|
|
331
|
+
|
|
332
|
+
### Recipes
|
|
333
|
+
|
|
334
|
+
No Phase 3 recipes. Custom-charset setup is infrastructure shared by many
|
|
335
|
+
techniques and will be demonstrated in Phase 4+ recipe docs.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## charset_copy_rom_to_ram — Copy the character ROM into RAM
|
|
340
|
+
|
|
341
|
+
**Complexity:** low
|
|
342
|
+
**Region:** both
|
|
343
|
+
**Uses registers:** D018
|
|
344
|
+
**Requires:** cpu_io_port_bank
|
|
345
|
+
**Cost:** bytes_code=70, bytes_data=2048
|
|
346
|
+
**Cost basis:** derived-listing
|
|
347
|
+
**Cost measured on:** kickassembler-charset-copy-rom-to-ram (one-off copy)
|
|
348
|
+
|
|
349
|
+
### Why
|
|
350
|
+
|
|
351
|
+
The built-in font lives in ROM, so it cannot be edited in place, and
|
|
352
|
+
the VIC only sees it in banks 0 and 2 (`char_rom_under_vic`). A program
|
|
353
|
+
that wants the stock glyphs plus a few of its own, or the stock font in
|
|
354
|
+
VIC bank 1 or 3, needs a RAM copy. The copy is cheap and runs once, but
|
|
355
|
+
it is the one moment in most programs when the I/O chips leave the
|
|
356
|
+
address map, and that moment has a failure mode that stops the machine.
|
|
357
|
+
|
|
358
|
+
### How
|
|
359
|
+
|
|
360
|
+
1. `SEI`.
|
|
361
|
+
2. Read `$01`, clear bits 0-2, set `$33` (CHAREN low, HIRAM and LORAM
|
|
362
|
+
high): `$D000-$DFFF` now reads the character ROM. Keep bits 3-5 as
|
|
363
|
+
found (`cpu_io_port_bank`, "Preserving bits 3-5").
|
|
364
|
+
3. Copy 2 KB (`$D000-$D7FF`, the upper-case set) or 4 KB (both sets)
|
|
365
|
+
to a RAM address the VIC can reach and that is not the ROM shadow.
|
|
366
|
+
In bank 0 that means anything but `$1000-$1FFF`.
|
|
367
|
+
4. Read `$01`, clear bits 0-2, set `$37`: I/O is back.
|
|
368
|
+
5. `CLI`.
|
|
369
|
+
6. Point `$D018`'s character-base field at the copy. Only then edit
|
|
370
|
+
glyphs, or edit them first and switch afterwards; either order works
|
|
371
|
+
because the VIC is not reading the copy until the switch.
|
|
372
|
+
|
|
373
|
+
### Why it works
|
|
374
|
+
|
|
375
|
+
`$01` bit 2 (CHAREN) with HIRAM or LORAM set selects the character ROM
|
|
376
|
+
in the `$D000` window instead of the VIC, SID, CIAs and colour RAM. The
|
|
377
|
+
PLA switches on the next bus cycle. Writes in that window go to the RAM
|
|
378
|
+
underneath, not to the ROM and not to the I/O chips.
|
|
379
|
+
|
|
380
|
+
The interrupt flag is the whole safety of the technique. The KERNAL's
|
|
381
|
+
IRQ handler acknowledges CIA1 by reading `$DC0D`, scans the keyboard
|
|
382
|
+
through `$DC00`/`$DC01`, and a raster handler acknowledges the VIC by
|
|
383
|
+
writing `$D019`. While the ROM is mapped every one of those addresses
|
|
384
|
+
is a font byte. An acknowledge that never happens leaves the interrupt
|
|
385
|
+
line asserted, so the handler re-enters as soon as it returns, and the
|
|
386
|
+
copy loop never gets another cycle. Measured in VICE x64sc with the
|
|
387
|
+
recipe below: the copy done with interrupts enabled managed 30 of 256
|
|
388
|
+
loop iterations on PAL and 7 on NTSC before the first KERNAL IRQ, and
|
|
389
|
+
never advanced again; the handler ran 26 times in the 61,000 cycles a
|
|
390
|
+
watchdog NMI allowed it, and its keyboard scan queued one phantom key.
|
|
391
|
+
Pitfall `irq_during_charen_window` has the detail. A program that has
|
|
392
|
+
replaced the KERNAL IRQ with its own handler is in the same position if
|
|
393
|
+
that handler touches any I/O register, which every raster handler does.
|
|
394
|
+
|
|
395
|
+
### Variations
|
|
396
|
+
|
|
397
|
+
**Copy one set only.** The upper-case/graphics set is `$D000-$D7FF`,
|
|
398
|
+
the lower-case set `$D800-$DFFF`. Most programs want one; eight
|
|
399
|
+
load/store pairs in the loop instead of sixteen.
|
|
400
|
+
|
|
401
|
+
**Copy with the display blanked.** Every badline the copy spans costs
|
|
402
|
+
about 40 cycles. Clear `$D011` bit 4, wait for a frame past line `$30`
|
|
403
|
+
so the VIC samples DEN off, copy, restore. Or do the copy before the
|
|
404
|
+
display is switched on at all, which is where a loader or a title
|
|
405
|
+
screen usually has it anyway.
|
|
406
|
+
|
|
407
|
+
**Copy under a raster interrupt.** If a raster IRQ is already running,
|
|
408
|
+
`SEI` alone is not enough for a long copy: the interrupt is only
|
|
409
|
+
deferred, and the frame it was meant to split is torn. Stop the raster
|
|
410
|
+
IRQ (clear `$D01A` bit 0, acknowledge `$D019`), copy, restart it. The
|
|
411
|
+
copy is under 40,000 cycles, two PAL frames, so the interruption is one
|
|
412
|
+
or two frames of the default picture.
|
|
413
|
+
|
|
414
|
+
**Pointer loop.** In C, `memcpy(dst, (const char *)0xd000, 2048)`
|
|
415
|
+
between the two `$01` writes does the same job; the compiler's loop is
|
|
416
|
+
slower than the unrolled indexed one but the difference is one frame.
|
|
417
|
+
|
|
418
|
+
### Cycle budget
|
|
419
|
+
|
|
420
|
+
Measured in VICE x64sc with the CIA2 timers, the 17-cycle timing window
|
|
421
|
+
subtracted, in the recipe below: the 2 KB copy costs 19,733 cycles with
|
|
422
|
+
the display blanked on both models, which is exactly the instruction
|
|
423
|
+
table (eight `LDA abs,X` / `STA abs,X` pairs, `INX`, `BNE`, 256 times,
|
|
424
|
+
plus 22 for the two `$01` switches). With the display on it is 20,784
|
|
425
|
+
on PAL and 21,150 on NTSC, the difference being the badlines the window
|
|
426
|
+
spans. The 4 KB copy with the display on is 39,580 PAL, 40,267 NTSC;
|
|
427
|
+
arithmetic puts it at 38,165 with the display blanked, not measured
|
|
428
|
+
here. Two figures from earlier builds are worth knowing: a copy routine
|
|
429
|
+
that holds `SEI` and `CLI` inside the timed window reads about 470
|
|
430
|
+
cycles high, because the KERNAL IRQ that fell due during the copy runs
|
|
431
|
+
at the `CLI`; and a loop whose `BNE` crosses a page boundary reads 255
|
|
432
|
+
high (pitfall `branch_page_cross_extra_cycle`). The Cost line above is
|
|
433
|
+
the built 2 KB routine's code and the RAM the copy occupies; the copy
|
|
434
|
+
runs once, so it carries no per-frame figure.
|
|
435
|
+
|
|
436
|
+
### Recipes
|
|
437
|
+
|
|
438
|
+
- `recipes/kickassembler/charset-copy-rom-to-ram.md`: the copy,
|
|
439
|
+
checked against a host checksum of the ROM, timed three ways, the
|
|
440
|
+
VIC pointed at it, and then done once without `SEI` under a watchdog
|
|
441
|
+
NMI so the hang is measured rather than described.
|
|
442
|
+
- `recipes/kickassembler/big-font-scroller.md` and
|
|
443
|
+
`recipes/oscar64/load-asset-runtime.md` do the copy inline as a step
|
|
444
|
+
of something else.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## screen_ram_relocation — Move screen RAM via $D018 hi-nibble
|
|
449
|
+
|
|
450
|
+
**Complexity:** low
|
|
451
|
+
**Region:** both
|
|
452
|
+
**Uses registers:** D018
|
|
453
|
+
|
|
454
|
+
### Why
|
|
455
|
+
|
|
456
|
+
The default screen RAM at $0400-$07E7 (1000 bytes for 40×25 characters) sits
|
|
457
|
+
inside VIC bank 0 but is also in the middle of the general-purpose RAM range.
|
|
458
|
+
Programs that want to use $0400-$07FF for code, sprite data, or other graphics
|
|
459
|
+
must relocate screen RAM. The mechanism is in $D018: the upper nibble (bits 4-7)
|
|
460
|
+
selects any 1 KB boundary inside the current VIC bank as the new screen RAM base.
|
|
461
|
+
|
|
462
|
+
### How
|
|
463
|
+
|
|
464
|
+
Bits 7-4 of $D018 (the VM field) hold a 4-bit value that selects the screen RAM
|
|
465
|
+
offset within the 16 KB VIC bank:
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
screen_ram_address_in_vic_bank = VM_bits * $0400
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
With VM_bits ranging from 0 to 15, the 16 possible positions are:
|
|
472
|
+
|
|
473
|
+
| VM bits (7-4) | Offset in VIC bank | CPU address in bank 0 |
|
|
474
|
+
|---------------|--------------------|-----------------------|
|
|
475
|
+
| 0000 | $0000 | $0000 |
|
|
476
|
+
| 0001 | $0400 | $0400 (default) |
|
|
477
|
+
| 0010 | $0800 | $0800 |
|
|
478
|
+
| 0011 | $0C00 | $0C00 |
|
|
479
|
+
| ... | ... | ... |
|
|
480
|
+
| 1111 | $3C00 | $3C00 |
|
|
481
|
+
|
|
482
|
+
The default KERNAL value of $D018 is $14 (binary 0001 0100), giving VM = %0001
|
|
483
|
+
= 1, which places screen RAM at offset $0400 within bank 0, i.e. CPU address
|
|
484
|
+
$0400. This is the "READY." screen you see at boot. ($14 is the value the KERNAL
|
|
485
|
+
writes; the register reads back as $15 because bit 0 is unused and reads 1 —
|
|
486
|
+
measured in VICE x64sc.)
|
|
487
|
+
|
|
488
|
+
Sprite pointers always follow the screen RAM: the 8 bytes at screen_base + $3F8
|
|
489
|
+
(screen_base + 1016) hold the sprite data-block pointers. When screen RAM moves,
|
|
490
|
+
sprite pointers move with it automatically — they are at a fixed offset from the
|
|
491
|
+
screen base, not at a fixed CPU address.
|
|
492
|
+
|
|
493
|
+
To move screen RAM, write a new value to $D018 keeping the CB bits unchanged:
|
|
494
|
+
read the register, mask bits 3-0 to preserve the char base, OR in the new VM
|
|
495
|
+
nibble shifted to bits 7-4, write back.
|
|
496
|
+
|
|
497
|
+
If KERNAL is still active (mode $37), the KERNAL's shadow of screen RAM location
|
|
498
|
+
at $0288 (HIBASE) should be updated to reflect the new screen RAM page. KERNAL
|
|
499
|
+
routines including CHROUT use $0288 to find the screen; if it is stale, KERNAL
|
|
500
|
+
output will write to the old location.
|
|
501
|
+
|
|
502
|
+
### Why it works
|
|
503
|
+
|
|
504
|
+
The VIC-II uses the VM bits to generate the high bits of its 14-bit address bus
|
|
505
|
+
during the video matrix fetch phase. During cycles 15-54 of each badline the VIC
|
|
506
|
+
fetches the 40 bytes for that character row from video_matrix_base + row*40 +
|
|
507
|
+
column into an internal latch, and reuses the latch for the row's remaining
|
|
508
|
+
seven lines; sprite pointers at video_matrix_base + $3F8 are fetched once per
|
|
509
|
+
raster line. (An earlier version said one byte was fetched on each character
|
|
510
|
+
clock of the visible area, which is not how the video matrix is read.)
|
|
511
|
+
Only the upper bits of that address come from VM; the lower 10 bits are the
|
|
512
|
+
running character clock counter. The effect is that moving VM simply shifts the
|
|
513
|
+
entire screen fetch window by multiples of 1 KB within the VIC bank — a pure
|
|
514
|
+
hardware address-offset operation with no software overhead per character.
|
|
515
|
+
|
|
516
|
+
### Variations
|
|
517
|
+
|
|
518
|
+
**Screen at $3C00.** Placing screen RAM at the top of VIC bank 0 ($3C00-$3FE7,
|
|
519
|
+
the 1 KB block running to $3FFF; an earlier version said $3FEF, which is 1008 bytes)
|
|
520
|
+
frees the lower 15 KB for code and graphics. The 8 bytes of sprite pointers at
|
|
521
|
+
$3FF8-$3FFF are conveniently at the very top of the bank. This is a common
|
|
522
|
+
layout for demos that use a full custom layout in bank 0.
|
|
523
|
+
|
|
524
|
+
**Double-buffered screen RAM.** (Worked through, with the sprite pointer
|
|
525
|
+
mirror and a measured recipe, as `screen_double_buffer_d018` below.) Two
|
|
526
|
+
screen buffers can live at different VM
|
|
527
|
+
offsets (e.g., $0000 and $0400 within the bank). The visible buffer flips by
|
|
528
|
+
changing VM bits; the invisible buffer is updated by the CPU. This avoids all
|
|
529
|
+
screen-tearing artifacts on text-mode displays; the flip lands at the next
|
|
530
|
+
badline, so write $D018 during the border or vertical blank for a whole-frame
|
|
531
|
+
swap.
|
|
532
|
+
|
|
533
|
+
### Cycle budget
|
|
534
|
+
|
|
535
|
+
A VM change is not seen until the next badline: the VIC fetches the 40
|
|
536
|
+
video-matrix bytes only during cycles 15-54 of a badline and reuses that latch
|
|
537
|
+
for the remaining seven lines of the character row, so a $D018 write mid-row
|
|
538
|
+
leaves the current row on the old base and moves the display from the next row
|
|
539
|
+
down (measured in VICE x64sc: written on line 54, effective from line 59).
|
|
540
|
+
Sprite pointers are the exception — they are fetched every line and follow the
|
|
541
|
+
new base from the next line. There is no cycle overhead beyond the write. (An
|
|
542
|
+
earlier version said the write took effect at the next video matrix fetch, which
|
|
543
|
+
read as "immediately".)
|
|
544
|
+
|
|
545
|
+
### Recipes
|
|
546
|
+
|
|
547
|
+
No Phase 3 recipes. This technique is infrastructure used by other Phase 4+
|
|
548
|
+
recipe docs.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## bitmap_relocation — Move bitmap base via $D018 lo-nibble
|
|
553
|
+
|
|
554
|
+
**Complexity:** low
|
|
555
|
+
**Region:** both
|
|
556
|
+
**Uses registers:** D018
|
|
557
|
+
|
|
558
|
+
### Why
|
|
559
|
+
|
|
560
|
+
Standard C64 bitmap modes (hires and multicolor) require an 8 KB block of RAM
|
|
561
|
+
holding the pixel data. With the VIC bank being only 16 KB wide, there are only
|
|
562
|
+
two valid positions for the bitmap within the bank. Programs need to know which
|
|
563
|
+
positions are available and how to select between them.
|
|
564
|
+
|
|
565
|
+
### How
|
|
566
|
+
|
|
567
|
+
In bitmap mode, bits 3-1 of $D018 (the CB field) serve a reduced role: only bit 3
|
|
568
|
+
(CB2, the high bit of the three-bit CB field) is meaningful. It selects between
|
|
569
|
+
two possible bitmap base addresses within the VIC bank:
|
|
570
|
+
|
|
571
|
+
| $D018 bit 3 | Bitmap base offset in VIC bank |
|
|
572
|
+
|-------------|-------------------------------|
|
|
573
|
+
| 0 | $0000 |
|
|
574
|
+
| 1 | $2000 |
|
|
575
|
+
|
|
576
|
+
The lower two CB bits (bits 2-1) are ignored in bitmap mode; they still affect
|
|
577
|
+
character base selection in text mode but have no effect on the bitmap address.
|
|
578
|
+
|
|
579
|
+
For VIC bank 0, the two legal bitmap positions in CPU address space are:
|
|
580
|
+
- Bit 3 = 0: bitmap at $0000-$1FFF
|
|
581
|
+
- Bit 3 = 1: bitmap at $2000-$3FFF
|
|
582
|
+
|
|
583
|
+
Screen RAM (the color/nybble data in standard bitmap mode) is positioned
|
|
584
|
+
independently via the VM bits (bits 7-4), as described in screen_ram_relocation.
|
|
585
|
+
A common layout for hires bitmap in bank 0 is bitmap at $2000 (bit 3 = 1) with
|
|
586
|
+
screen RAM at $0400 (VM = 1). The $D018 value for this is $18 (bitmap bit set,
|
|
587
|
+
VM = 1): binary 0001 1000.
|
|
588
|
+
|
|
589
|
+
### Why it works
|
|
590
|
+
|
|
591
|
+
In bitmap mode the VIC-II generates pixel addresses differently from text mode.
|
|
592
|
+
Instead of a separate character fetch to look up a bitmap address, the VIC
|
|
593
|
+
directly computes: bitmap_base + (character_row * 320) + (character_column * 8)
|
|
594
|
+
+ scan_line_within_row. The bitmap base is the one degree of freedom in this
|
|
595
|
+
computation, and it is encoded in a single bit of $D018 because only powers of
|
|
596
|
+
two from 0 to 8192 are legal — an 8 KB block must land on an 8 KB boundary.
|
|
597
|
+
|
|
598
|
+
### Variations
|
|
599
|
+
|
|
600
|
+
**FLI (Flexible Line Interpretation).** FLI forces a badline on every raster
|
|
601
|
+
line by writing $D011 (YSCROLL = line & 7) so the badline condition becomes true
|
|
602
|
+
on cycle 15; each forced badline re-fetches the 40 video-matrix bytes
|
|
603
|
+
(c-accesses, cycles 15-54) from whatever page the VM nibble of $D018 selects, so
|
|
604
|
+
with $D018 changed once per line (before the $D011 write) each 8×1 strip gets its
|
|
605
|
+
own colour bytes. The cycle-exact write is the $D011 one; $D018 only has to be
|
|
606
|
+
in place before it. The bitmap base is not changed. See fli_image in
|
|
607
|
+
bitmap-modes.md and recipes/kickassembler/fli-image.md. (An earlier version of
|
|
608
|
+
this paragraph described FLI as a cycle-exact $D018 write during an idle fetch
|
|
609
|
+
and did not mention $D011 or the forced badline.)
|
|
610
|
+
|
|
611
|
+
**Bitmap at $0000 and sprite multiplexing.** The $0000-$1FFF bitmap position
|
|
612
|
+
overlaps with zero page and the stack ($0000-$01FF). Sprites whose data blocks
|
|
613
|
+
land in $0000-$1FFF are valid as long as the sprite pointer value accounts for
|
|
614
|
+
the collision. In practice most demos use $2000 for the bitmap and leave
|
|
615
|
+
$0000-$1FFF for code, zero-page variables, and stack.
|
|
616
|
+
|
|
617
|
+
### Cycle budget
|
|
618
|
+
|
|
619
|
+
No per-line cycle cost. The bitmap base is a one-time configuration write.
|
|
620
|
+
FLI timing is covered under `fli_image` in `docs/techniques/bitmap-modes.md`
|
|
621
|
+
(Cycle budget (PAL)) and in `docs/recipes/kickassembler/fli-image.md`. (An
|
|
622
|
+
earlier version pointed at the raster techniques doc, which declares FLI out of
|
|
623
|
+
scope.)
|
|
624
|
+
|
|
625
|
+
### Recipes
|
|
626
|
+
|
|
627
|
+
No Phase 3 recipes. Bitmap setup will be demonstrated in Phase 4+ recipe docs.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## cartridge_bank_easyflash — EasyFlash cart bank switching
|
|
632
|
+
|
|
633
|
+
**Complexity:** medium
|
|
634
|
+
**Region:** both
|
|
635
|
+
|
|
636
|
+
### Why
|
|
637
|
+
|
|
638
|
+
A stock C64 program is limited to 64 KB of addressable memory with roughly
|
|
639
|
+
38-52 KB free for game or demo code and data, depending on which ROMs are banked
|
|
640
|
+
out. EasyFlash cartridges expand this by providing up to 1 MB of flash memory
|
|
641
|
+
organized as up to 64 banks of 16 KB each. The cartridge presents two 8 KB ROM
|
|
642
|
+
windows simultaneously (LOROM at $8000-$9FFF and HIROM at $A000-$BFFF), and
|
|
643
|
+
software selects which 16 KB bank to page in by writing to the cartridge's bank
|
|
644
|
+
register. The entire flash contents become accessible as a sequence of banked
|
|
645
|
+
8 KB ROM windows, making EasyFlash the simplest way to distribute a large game
|
|
646
|
+
or demo that exceeds the 64 KB address space.
|
|
647
|
+
|
|
648
|
+
### How
|
|
649
|
+
|
|
650
|
+
An EasyFlash does not power up in 16 KB mode with the KERNAL present, as an
|
|
651
|
+
earlier version of this section said. With boot enabled (the EasyFlash jumper in
|
|
652
|
+
its start position; VICE's default, `-easyflashjumper` disables it and the
|
|
653
|
+
machine boots to BASIC with the cartridge invisible) the cartridge comes up in
|
|
654
|
+
Ultimax mode: bank 0's HIROM is mapped at $E000-$FFFF (ROML at $8000 in Ultimax
|
|
655
|
+
per the memory-map reference; not measured here), the CPU fetches its reset
|
|
656
|
+
vector from $FFFC of that HIROM, and the KERNAL is not mapped. The startup code
|
|
657
|
+
therefore lives in bank 0 HIROM, not LOROM. It typically copies a stub to RAM
|
|
658
|
+
and writes $07 to $DE02 (MODE=1, EXROM=1, GAME=1 — bit set means the line is
|
|
659
|
+
asserted) to enter 16 KB mode, in which:
|
|
660
|
+
|
|
661
|
+
- $8000-$9FFF is LOROM (cartridge ROM, low bank)
|
|
662
|
+
- $A000-$BFFF is HIROM (cartridge ROM, high bank)
|
|
663
|
+
- $D000-$DFFF is I/O (as normal when CHAREN = 1)
|
|
664
|
+
- $E000-$FFFF is KERNAL ROM
|
|
665
|
+
|
|
666
|
+
Measured in VICE x64sc 3.10 with a two-bank type-$0020 .CRT: a CBM80 stub in
|
|
667
|
+
bank 0 LOROM is never executed at power-on (the same stub in a generic 16 KB
|
|
668
|
+
.CRT is), the HIROM reset vector is, and the CRT header's EXROM/GAME bytes do
|
|
669
|
+
not change this.
|
|
670
|
+
|
|
671
|
+
The EasyFlash hardware decodes two I/O addresses in expansion area 1:
|
|
672
|
+
|
|
673
|
+
| Register | Address | Function |
|
|
674
|
+
|-----------|---------|---------------------------------------------------|
|
|
675
|
+
| EF_BANK | $DE00 | Select the active 16 KB flash bank (0-63) |
|
|
676
|
+
| EF_CONTROL| $DE02 | Control: GAME (bit 0), EXROM (bit 1), MODE (bit 2), LED (bit 7) |
|
|
677
|
+
|
|
678
|
+
Writing a bank number (0-63) to $DE00 immediately pages in the corresponding
|
|
679
|
+
16 KB chunk of flash. The low 8 KB of that chunk appears at $8000-$9FFF; the
|
|
680
|
+
high 8 KB appears at $A000-$BFFF. This happens on the next CPU cycle — the switch
|
|
681
|
+
is instantaneous.
|
|
682
|
+
|
|
683
|
+
Bank 0 is the entry bank: it is active when the cartridge powers on and its
|
|
684
|
+
HIROM (not LOROM, as an earlier version said — see above) contains the reset
|
|
685
|
+
vector and the startup and loader stub. The remaining banks hold game
|
|
686
|
+
chapters, level data, music, graphics, or further code segments. The cartridge
|
|
687
|
+
author decides how to partition and use the 64 banks.
|
|
688
|
+
|
|
689
|
+
$DE02 is the control register: bit 0 = GAME, bit 1 = EXROM (1 = line asserted),
|
|
690
|
+
bit 2 = MODE, bit 7 = LED. Bit 1 always drives EXROM, but bit 0 drives GAME only
|
|
691
|
+
while bit 2 is set; with bit 2 clear, GAME follows the cartridge's boot jumper
|
|
692
|
+
instead, so on a cartridge set to boot, writes of $00-$03 only ever give Ultimax
|
|
693
|
+
($00, $01) or 16 KB ($02, $03) and neither 8 KB nor off can be selected. Always
|
|
694
|
+
write with bit 2 set: $07 = 16 KB, $06 = 8 KB, $05 = Ultimax, $04 = cartridge
|
|
695
|
+
off. Bit names from Oscar64's easyflash.h; mode table measured in VICE x64sc
|
|
696
|
+
(3.10) against its EasyFlash emulation, not against hardware. (An earlier
|
|
697
|
+
version described $DE02 as a two-bit GAME/EXROM register and omitted MODE.) For
|
|
698
|
+
standard EasyFlash operation during gameplay the register is left at $07.
|
|
699
|
+
|
|
700
|
+
### Why it works
|
|
701
|
+
|
|
702
|
+
The C64 cartridge port exposes the full 16-bit address bus, the data bus, the
|
|
703
|
+
GAME and EXROM input lines, the phi-2 clock, and dedicated ROML/ROMH strobe
|
|
704
|
+
outputs. The PLA generates ROML and ROMH based on address range and the
|
|
705
|
+
GAME/EXROM configuration. EasyFlash decodes ROML and ROMH as chip-selects for
|
|
706
|
+
its flash memory array. A small CPLD or FPGA on the cartridge board intercepts
|
|
707
|
+
$DE00 writes and stores the bank number in a latch; on every subsequent ROML or
|
|
708
|
+
ROMH access the latch value is OR'd onto the higher address lines of the flash
|
|
709
|
+
chip, selecting which physical page of flash responds to that $8000 or $A000
|
|
710
|
+
access. The CPU address bus sees ROM at the same two fixed windows; only the
|
|
711
|
+
source flash page changes.
|
|
712
|
+
|
|
713
|
+
### Variations
|
|
714
|
+
|
|
715
|
+
**Self-modifying cartridge ROM.** Because the C64 PLA disables RAM CAS on reads
|
|
716
|
+
in cartridge ROM ranges but writes still reach RAM, any write to $8000-$BFFF
|
|
717
|
+
(while cartridge ROM is mapped there) actually writes to the underlying RAM.
|
|
718
|
+
This allows a program to maintain RAM buffers at $8000-$BFFF and bank the
|
|
719
|
+
cartridge ROM out ($DE02 = $04) when the program needs to write
|
|
720
|
+
them back, then bank cartridge ROM in for read-only access. Advanced EasyFlash
|
|
721
|
+
programs use this technique for per-level score tables, save states, and
|
|
722
|
+
configuration data.
|
|
723
|
+
|
|
724
|
+
**EasyFlash 3 and extended bank space.** Successor cartridges (EF3) support
|
|
725
|
+
more than 64 banks through an additional high-bank register and provide USB
|
|
726
|
+
or SD card access, but the basic $DE00 bank-switch mechanism is the same.
|
|
727
|
+
|
|
728
|
+
### Cycle budget
|
|
729
|
+
|
|
730
|
+
No cycle constraints for bank switching itself. The bank latch takes the value
|
|
731
|
+
on the write cycle of the STA $DE00 — the instruction's last cycle — so the STA
|
|
732
|
+
itself always completes correctly (its opcode and operand were fetched before the
|
|
733
|
+
write) and a switch can never fall inside an instruction. The hazard is the cycle
|
|
734
|
+
after it: the very next opcode fetch already comes from the new bank. Execute the
|
|
735
|
+
switching code from RAM, or from a region whose bytes are identical in every
|
|
736
|
+
bank, never from the $8000-$BFFF window being switched unless the code that
|
|
737
|
+
follows the STA is present at the same address in the target bank. The same
|
|
738
|
+
applies to $DE02 mode changes, which can swap $E000-$FFFF from HIROM to KERNAL
|
|
739
|
+
under the executing PC — which is why the EasyFlash start-up stub copies itself
|
|
740
|
+
to RAM before writing $DE02. (An earlier version advised aligning switches to
|
|
741
|
+
instruction boundaries against a mid-instruction switch, which cannot happen.)
|
|
742
|
+
|
|
743
|
+
### Recipes
|
|
744
|
+
|
|
745
|
+
- `recipes/kickassembler/easyflash-save.md` builds an EasyFlash `.crt` from one
|
|
746
|
+
listing and saves to flash through the Am29F040 commands (technique
|
|
747
|
+
`cartridge_save` on the file I/O page). An earlier version of this section
|
|
748
|
+
said no recipe covered EasyFlash.
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
752
|
+
## ram_under_kernal — Hide RAM under KERNAL ROM
|
|
753
|
+
**Demands:** kernal_rom_out
|
|
754
|
+
|
|
755
|
+
**Complexity:** medium
|
|
756
|
+
**Region:** both
|
|
757
|
+
**Claims:** irq_vector_fffe (owns), nmi_vector_fffa (owns)
|
|
758
|
+
**Claims basis:** estimated
|
|
759
|
+
|
|
760
|
+
### Why
|
|
761
|
+
|
|
762
|
+
With KERNAL ROM banked in ($01 bit 1 = 1), the 8 KB region from $E000 to $FFFF
|
|
763
|
+
reads as ROM. But the underlying RAM at those addresses is still physically
|
|
764
|
+
present and is still written by any STA into that range. Banking KERNAL out
|
|
765
|
+
(bit 1 = 0) exposes that RAM to the CPU, providing 8 KB of additional work RAM
|
|
766
|
+
above and beyond the normal ~38 KB free. This is used for large data buffers,
|
|
767
|
+
custom IRQ and NMI handlers that must live at $FFFA-$FFFF, KERNAL replacement,
|
|
768
|
+
and packing maximum data into a 64 KB build.
|
|
769
|
+
|
|
770
|
+
### How
|
|
771
|
+
|
|
772
|
+
The HIRAM bit is bit 1 of $01. Setting it to 0 disables KERNAL ROM and exposes
|
|
773
|
+
the 8 KB RAM from $E000 to $FFFF. Bit 0 (LORAM) does not need to change:
|
|
774
|
+
KERNAL ROM visibility is controlled solely by HIRAM when no cartridge is present.
|
|
775
|
+
|
|
776
|
+
The standard sequence for switching to mode $35 (I/O visible, all ROM banked out):
|
|
777
|
+
|
|
778
|
+
1. Disable IRQs with SEI (mandatory — KERNAL IRQ handler at $EA31 will be gone).
|
|
779
|
+
2. Write the address of the new IRQ handler into RAM at $FFFE/$FFFF. The bytes
|
|
780
|
+
written land in RAM even while KERNAL is still visible on reads.
|
|
781
|
+
3. Write the address of the new NMI handler into RAM at $FFFA/$FFFB.
|
|
782
|
+
4. Write $35 to $01 (LORAM=1, HIRAM=0, CHAREN=1 — I/O visible, no ROM).
|
|
783
|
+
5. Re-enable IRQs with CLI once the new handler is in place.
|
|
784
|
+
|
|
785
|
+
From this point the $E000-$FFFF range reads back the RAM values written in steps
|
|
786
|
+
2 and 3. The CPU correctly fetches interrupt vectors from $FFFA-$FFFF and they
|
|
787
|
+
point to the program's custom handlers.
|
|
788
|
+
|
|
789
|
+
If the program needs to call any KERNAL routine (CHROUT, CHKIN, OPEN, etc.),
|
|
790
|
+
it must copy those routines into RAM before banking KERNAL out, or temporarily
|
|
791
|
+
bank KERNAL back in with interrupts disabled, call the routine, and bank it
|
|
792
|
+
out again.
|
|
793
|
+
|
|
794
|
+
### Why it works
|
|
795
|
+
|
|
796
|
+
All ROM banking on the C64 is read-only from the PLA's perspective. The PLA
|
|
797
|
+
asserts or deasserts chip-select lines for ROM reads only; it never generates
|
|
798
|
+
a ROM chip-select for write cycles. Writes to $E000-$FFFF always reach the
|
|
799
|
+
underlying RAM regardless of the HIRAM bit. This is the same property that
|
|
800
|
+
applies to BASIC ROM at $A000-$BFFF and char ROM at $D000-$DFFF. The distinction
|
|
801
|
+
is that KERNAL ROM also contains the hardware interrupt vectors at $FFFA-$FFFF,
|
|
802
|
+
so when KERNAL is banked out and the CPU takes an IRQ or NMI, it reads those
|
|
803
|
+
vector addresses from RAM — which must already contain valid handler addresses
|
|
804
|
+
before the bank switch happens.
|
|
805
|
+
|
|
806
|
+
### Variations
|
|
807
|
+
|
|
808
|
+
**KERNAL replacement.** Entire custom KERNAL images can be placed at
|
|
809
|
+
$E000-$FFFF by writing them in while HIRAM is still 1 (ROM wins on reads,
|
|
810
|
+
RAM accepts the writes), then clearing HIRAM to expose the RAM image. This
|
|
811
|
+
technique powers KERNAL replacement cartridges and some fastloader
|
|
812
|
+
implementations. The custom image must provide all jump-table entries at
|
|
813
|
+
$FF81-$FFF5 if any downstream code calls KERNAL via the standard jump table.
|
|
814
|
+
|
|
815
|
+
**RAM at $E000-$FFFF for music and graphics.** In a demo build where KERNAL is
|
|
816
|
+
never called and all interrupts are custom-managed, the full 64 KB becomes
|
|
817
|
+
available: mode $34 (no ROM, no I/O) gives $A000-$BFFF plus $E000-$FFFF plus
|
|
818
|
+
$D000-$DFFF as RAM, totaling 20 KB of previously-ROM space. I/O access in mode
|
|
819
|
+
$34 requires temporarily toggling CHAREN: write $35 to $01 for a VIC/SID/CIA
|
|
820
|
+
access, then write $34 back. Most demo engines instead use mode $35 (I/O always
|
|
821
|
+
visible) and accept that $D000-$DFFF is occupied by I/O rather than RAM.
|
|
822
|
+
|
|
823
|
+
**The stack cannot move under the KERNAL.** The stack cannot leave page 1: S is
|
|
824
|
+
8 bits and the 6510 fixes stack accesses to $0100-$01FF, so RAM under the KERNAL
|
|
825
|
+
cannot host a hardware stack. Engines short of stack keep page 1 free of other
|
|
826
|
+
data and use a software stack (indexed store through a pointer) for bulk state.
|
|
827
|
+
Measured: TXS with X=$50 followed by PHA writes $0150 (VICE x64sc). (An earlier
|
|
828
|
+
version of this variation claimed a secondary stack could be relocated to $E1xx
|
|
829
|
+
via TXS/TSX; it cannot.)
|
|
830
|
+
|
|
831
|
+
### Cycle budget
|
|
832
|
+
|
|
833
|
+
No special cycle budget. Switching HIRAM is a single STA $01. The KERNAL
|
|
834
|
+
is gone from the very next read after the write completes.
|
|
835
|
+
|
|
836
|
+
### Recipes
|
|
837
|
+
|
|
838
|
+
No Phase 3 recipes. Custom-IRQ-with-RAM-KERNAL infrastructure is prerequisite
|
|
839
|
+
to the raster and sprite recipes landing in Phase 4+.
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
## screen_double_buffer_d018 — Screen double buffer via $D018
|
|
844
|
+
|
|
845
|
+
**Complexity:** medium
|
|
846
|
+
**Region:** both
|
|
847
|
+
**Uses registers:** D018, D011
|
|
848
|
+
**Requires:** screen_ram_relocation
|
|
849
|
+
|
|
850
|
+
### Why
|
|
851
|
+
|
|
852
|
+
A program that redraws most of the text screen every frame cannot finish
|
|
853
|
+
before the VIC reads the cells it is still writing. The result is a frame
|
|
854
|
+
that is half old and half new. Two screen pages fix it: the CPU draws into
|
|
855
|
+
the page the VIC is not showing, then one write to $D018 swaps them. The
|
|
856
|
+
swap is atomic from the viewer's side, so a frame is always whole.
|
|
857
|
+
|
|
858
|
+
### How
|
|
859
|
+
|
|
860
|
+
1. Pick two 1 KB pages in the same VIC bank. Each starts on a 1 KB boundary
|
|
861
|
+
and is selected by the VM nibble (bits 7-4 of $D018), so the two flip
|
|
862
|
+
values differ only in their upper nibble. The pages must not overlap the
|
|
863
|
+
character set (the ROM font the VIC sees at $1000-$1FFF in banks 0 and
|
|
864
|
+
2, or a 2 KB custom set) or a bitmap. The recipe below uses $2800 (VM 10)
|
|
865
|
+
and $2C00 (VM 11) in bank 0 with the ROM font, flip values $A4 and $B4.
|
|
866
|
+
2. Keep an index `hidden`. Every draw of the frame goes to `page[hidden]`
|
|
867
|
+
and nowhere else.
|
|
868
|
+
3. When the frame is drawn, wait for the vertical blank and write
|
|
869
|
+
`(vm[hidden] << 4) | charset_bits` to $D018. Then `hidden ^= 1`. The
|
|
870
|
+
toggle sits next to the write so no draw can land between them.
|
|
871
|
+
4. Before the flip, make the hidden page's sprite pointer block (bytes
|
|
872
|
+
+$3F8 to +$3FF) equal to the visible page's. The VIC reads the pointers
|
|
873
|
+
from whichever page is on display.
|
|
874
|
+
|
|
875
|
+
Waiting for the blank: poll bit 7 of $D011 (RST8). It sets on line 256,
|
|
876
|
+
after the last display line (250 for 25 rows), and clears at line 0. Wait
|
|
877
|
+
for it to be clear and then set, not just set: a draw that finishes inside
|
|
878
|
+
the bottom border would otherwise see RST8 already high and flip twice in
|
|
879
|
+
one frame. Oscar64's `vic_waitFrame()` does exactly this (`vic.c`: while
|
|
880
|
+
RST8 set, then while clear).
|
|
881
|
+
|
|
882
|
+
### Why it works
|
|
883
|
+
|
|
884
|
+
The VM nibble is only read when the VIC fetches the video matrix, on a
|
|
885
|
+
badline, and the 40 bytes are held in a latch for the rest of the character
|
|
886
|
+
row (`screen_ram_relocation`, measured there: written on line 54, effective
|
|
887
|
+
from line 59). A write in the blank is therefore complete before the first
|
|
888
|
+
badline of the next frame, and the whole frame comes from the new page.
|
|
889
|
+
The sprite pointers are the exception: they are fetched every raster line
|
|
890
|
+
from `visible_page + $3F8`, so a flip moves them on the next line. That is
|
|
891
|
+
why the hidden page's pointer block has to be right before the flip and
|
|
892
|
+
not after.
|
|
893
|
+
|
|
894
|
+
Colour RAM at $D800 is not selected by $D018. There is one 1,000-byte
|
|
895
|
+
colour map and both pages share it. A colour written while page A is shown
|
|
896
|
+
changes A's cell now and B's cell after the flip. Three ways to live with
|
|
897
|
+
that: keep the colour map fixed and change only characters (the recipe
|
|
898
|
+
sets all 1,000 cells white once); write colour changes in the blank, after
|
|
899
|
+
the flip, so the character and its colour arrive together; or confine
|
|
900
|
+
colour changes to cells whose character is the same on both pages. The
|
|
901
|
+
blank is short for a full colour rewrite. By arithmetic from 312 lines at
|
|
902
|
+
63 cycles and 263 lines at 65: lines 251-311 plus 0-50 give 112 lines,
|
|
903
|
+
about 7,000 cycles on PAL; 251-262 plus 0-50 give 63 lines, about 4,100
|
|
904
|
+
on NTSC. A 1,000-byte fill does not fit the NTSC figure with a C loop.
|
|
905
|
+
|
|
906
|
+
Frame parity. The page being drawn holds what was on screen two frames
|
|
907
|
+
ago, not one. A full redraw does not care. A partial (dirty-cell) update
|
|
908
|
+
does: each change has to be applied to both pages, one frame apart, or the
|
|
909
|
+
page you flip to shows a cell two frames stale. Keep a change list and
|
|
910
|
+
apply it to `page[hidden]` on two consecutive frames, or redraw everything.
|
|
911
|
+
|
|
912
|
+
Sprite pointers and libraries. Anything that writes sprite pointers
|
|
913
|
+
through a single screen address assumes one page. Oscar64's `vspr_init(char
|
|
914
|
+
* screen)` stores `screen + 0x3f8` once and bakes the absolute address of
|
|
915
|
+
that block into its raster IRQ entries (`c64/sprites.c`); `spr_init` keeps
|
|
916
|
+
the same single pointer. `vspr_screen(char * screen)` re-points them. With
|
|
917
|
+
two pages, either keep both blocks identical by hand after every image
|
|
918
|
+
change, or call `vspr_screen()` with the page about to be shown before the
|
|
919
|
+
update that writes the pointers. The second path is not measured here.
|
|
920
|
+
The cheapest failure is a clear routine that works in whole 1 KB pages:
|
|
921
|
+
bytes 1000 to 1023 hold the pointer block, and a fill that runs to the end
|
|
922
|
+
of the page rewrites it every frame. The companion recipe shows the sprite
|
|
923
|
+
that results.
|
|
924
|
+
|
|
925
|
+
If the KERNAL prints to the screen (CHROUT), it prints to the page named by
|
|
926
|
+
HIBASE ($0288), which is one page. Set $0288 to the hidden page's high byte
|
|
927
|
+
before printing. Not measured here.
|
|
928
|
+
|
|
929
|
+
### Variations
|
|
930
|
+
|
|
931
|
+
**Two bitmap pages.** A bitmap needs 8 KB and can only sit at offset $0000
|
|
932
|
+
or $2000 in a bank, so two full bitmaps plus their matrices do not fit one
|
|
933
|
+
bank; the usual layout is two banks and a $DD00 write alongside the $D018
|
|
934
|
+
one (`effects-vector-3d.md`, Double buffering; `bitmap-modes.md`,
|
|
935
|
+
Two-bitmap pages).
|
|
936
|
+
|
|
937
|
+
**Two pages, one charset, two fonts.** With two pages the CB bits can
|
|
938
|
+
differ between the two flip values as well, so each page can carry its own
|
|
939
|
+
2 KB character set. The cost is 4 KB of charset instead of 2 KB and the
|
|
940
|
+
same 1 KB rule for the pages.
|
|
941
|
+
|
|
942
|
+
**Triple buffering.** A third page lets the draw run more than one frame
|
|
943
|
+
without stalling the flip. Three 1 KB pages plus the ROM font still fit
|
|
944
|
+
bank 0. Not built here.
|
|
945
|
+
|
|
946
|
+
### Cycle budget
|
|
947
|
+
|
|
948
|
+
The flip is one store. The draw has one frame minus nothing: it runs during
|
|
949
|
+
the display, so badline and sprite DMA cycles come out of it. Measured in
|
|
950
|
+
VICE x64sc 3.10 with the recipe below at `-O2`: a whole-page fill from a
|
|
951
|
+
256-byte template (`LDA (zp),y` plus four `STA abs,y`, 30 cycles per four
|
|
952
|
+
bytes) plus a 30-cell caption costs 12,598 cycles on PAL and 13,165 on
|
|
953
|
+
NTSC, the difference being the badlines and sprite fetches it overlapped.
|
|
954
|
+
Both are under a frame (19,656 PAL, 17,095 NTSC), and two runs one PAL
|
|
955
|
+
frame apart show consecutive frame numbers on opposite pages. The first
|
|
956
|
+
draft filled the page with `memcpy` and `memset` in 40-byte rows at 17,437
|
|
957
|
+
and 22,760 cycles; PAL still alternated on the short draw, and NTSC showed
|
|
958
|
+
the same frame number in two runs one frame apart, because neither draw
|
|
959
|
+
fit in 17,095 cycles. If the counter stops advancing once per frame, the
|
|
960
|
+
draw is too slow, not the flip.
|
|
961
|
+
|
|
962
|
+
### Recipes
|
|
963
|
+
|
|
964
|
+
- `recipes/oscar64/double-buffer.md` — two pages, one sprite, flip every frame, pointer block mirrored
|
|
965
|
+
- `recipes/oscar64/double-buffer-nomirror.md` — the same listing without the mirror, and the sprite it shows
|
|
966
|
+
|
|
967
|
+
## memory_layout_plan — Plan the memory map before the first build
|
|
968
|
+
|
|
969
|
+
**Complexity:** low
|
|
970
|
+
**Uses registers:** D018, DD00
|
|
971
|
+
|
|
972
|
+
**Why.** The toolchain places code where it likes; the VIC-II does not.
|
|
973
|
+
A music player expects `$1000`, a charset must start on a 2 KB boundary
|
|
974
|
+
inside the VIC's 16 KB bank, a screen on a 1 KB boundary, a sprite on 64
|
|
975
|
+
bytes, and none of them may overlap code that grows with the next
|
|
976
|
+
feature. A layout decided after the fact produces the collision the
|
|
977
|
+
`charset_blit_overruns_grown_code` pitfall describes: a build that boots
|
|
978
|
+
and shows garbage where the font was.
|
|
979
|
+
|
|
980
|
+
**How.** List every asset with a fixed address or alignment; choose the
|
|
981
|
+
VIC bank; place the aligned assets first, largest alignment first; then
|
|
982
|
+
let code and data fill what remains, and read the map file back after
|
|
983
|
+
every build to see that nothing moved into a reserved range. Write the
|
|
984
|
+
plan down as the toolchain's own words: Oscar64 regions and sections,
|
|
985
|
+
the cc65 linker configuration, KickAssembler `.pc` and segments. The
|
|
986
|
+
toolchain page `toolchains/memory-layout-planning.md` walks through it
|
|
987
|
+
for all three.
|
|
988
|
+
|
|
989
|
+
**Why it works.** The VIC reads through its own 16 KB window with fixed
|
|
990
|
+
alignments set by `$D018` and the bank bits of `$DD00`; the CPU sees the
|
|
991
|
+
whole 64 KB and does not care where anything is. Planning the VIC's
|
|
992
|
+
constraints first and letting the CPU's flexible material fill the gaps
|
|
993
|
+
means the constraints are met by construction, not by luck.
|
|
994
|
+
|
|
995
|
+
**Variations.** Two VIC banks with the assets split between them; data
|
|
996
|
+
under the ROMs for the CPU only (`ram_under_kernal`); a loader that owns
|
|
997
|
+
the top of RAM, which the plan leaves free.
|
|
998
|
+
|
|
999
|
+
**Cycle budget.** None: this is a build-time decision.
|
|
1000
|
+
|
|
1001
|
+
### Recipes
|
|
1002
|
+
|
|
1003
|
+
- `recipes/oscar64/memory-layout.md` — stub, music, charset, sprite, screen and code at planned addresses, printing each symbol's address so the screen can be read against the map
|
|
1004
|
+
|
|
1005
|
+
## irq_owns_processor_port — Interrupt handlers that save, set and restore $01
|
|
1006
|
+
|
|
1007
|
+
**Complexity:** medium
|
|
1008
|
+
**Region:** both
|
|
1009
|
+
**Uses registers:** D019, DD0D
|
|
1010
|
+
**Requires:** cpu_io_port_bank, ram_under_kernal
|
|
1011
|
+
**Demands:** kernal_rom_out
|
|
1012
|
+
**Cost:** cycles_per_frame=18
|
|
1013
|
+
**Cost basis:** measured-vice
|
|
1014
|
+
**Cost measured on:** kickassembler-irq-owns-port (per interrupt taken)
|
|
1015
|
+
|
|
1016
|
+
### Why
|
|
1017
|
+
|
|
1018
|
+
The 4 KB of RAM under `$D000-$DFFF` is only reachable with I/O banked
|
|
1019
|
+
out (`$01` = `$34`, or `$30`). The rule elsewhere on this page and in
|
|
1020
|
+
`pitfalls/banking.md` is `SEI` first, because an interrupt handler run
|
|
1021
|
+
in that state writes its acknowledge and its register updates into RAM.
|
|
1022
|
+
That rule is fine for a short poke. It is not fine for a decruncher or
|
|
1023
|
+
a loader filling 4 KB of level data, which takes frames: with `SEI` the
|
|
1024
|
+
music stops and the raster splits fall apart for the whole depack. The
|
|
1025
|
+
answer games and loaders use is to make every interrupt handler put I/O
|
|
1026
|
+
back itself, so main code can leave `$01` at `$34` with interrupts on.
|
|
1027
|
+
|
|
1028
|
+
### How
|
|
1029
|
+
|
|
1030
|
+
1. Bank the KERNAL out for good. Write the IRQ handler's address to
|
|
1031
|
+
`$FFFE/$FFFF` and the NMI handler's to `$FFFA/$FFFB`; the writes
|
|
1032
|
+
reach the RAM under the ROM (`ram_under_rom_traps`). With HIRAM
|
|
1033
|
+
clear, as it is in both `$34` and `$35`, the CPU takes its vectors
|
|
1034
|
+
from that RAM. The KERNAL's `$0314`/`$0318` vectors are never read.
|
|
1035
|
+
2. Every handler, IRQ and NMI alike, starts by saving `$01` and
|
|
1036
|
+
storing `#$35`:
|
|
1037
|
+
`PHA` / `LDA $01` / `PHA` / `LDA #$35` / `STA $01`.
|
|
1038
|
+
3. The handler does its work: acknowledge (`$D019`, `$DC0D` or
|
|
1039
|
+
`$DD0D`), raster writes, the music player.
|
|
1040
|
+
4. It ends by putting back exactly the value it found:
|
|
1041
|
+
`PLA` / `STA $01` / `PLA` / `RTI`.
|
|
1042
|
+
5. Main code may now set `$01` = `$34` and copy, decrunch or load into
|
|
1043
|
+
`$D000-$DFFF` with interrupts enabled, and set `$35` again when it
|
|
1044
|
+
needs I/O itself.
|
|
1045
|
+
|
|
1046
|
+
### Why it works
|
|
1047
|
+
|
|
1048
|
+
The PLA chip decodes the processor-port lines on every access, so the handler's store of `#$35`
|
|
1049
|
+
makes the VIC, SID and CIAs visible from its next instruction, and the
|
|
1050
|
+
restore hides them again before `RTI` returns to the interrupted copy.
|
|
1051
|
+
The copy never sees I/O, and the handler never sees RAM where it
|
|
1052
|
+
expects a register.
|
|
1053
|
+
|
|
1054
|
+
The restore must be the saved value, not a constant. The interrupted
|
|
1055
|
+
code may be mid-copy at `$34` or may itself be at `$35`; a handler that
|
|
1056
|
+
ends with `#$35` returns a `$34` copy into the I/O window, and the rest
|
|
1057
|
+
of the copy is written into the VIC, SID and CIA registers. Measured in
|
|
1058
|
+
VICE with the recipe below: the display turned to garbage and main code
|
|
1059
|
+
never finished its first pass.
|
|
1060
|
+
|
|
1061
|
+
The save goes on the stack, or in a byte of the handler's own. An NMI
|
|
1062
|
+
can land inside an IRQ handler's wrapper, between its `LDA $01` and its
|
|
1063
|
+
restore. With one shared save byte the NMI would overwrite the IRQ's
|
|
1064
|
+
saved `$34` with `$35`. With the stack each handler restores its own
|
|
1065
|
+
copy.
|
|
1066
|
+
|
|
1067
|
+
Store `#$35`, do not `ORA #$01`: from `$34` either gives `$35`, but
|
|
1068
|
+
from `$30` the `ORA` gives `$31`, which maps the character ROM, not I/O
|
|
1069
|
+
(`hardware/c64-registers-reference.md`, the `$01` bit description). `#$35` keeps bit 5 set, so the
|
|
1070
|
+
datasette motor stays off; it does overwrite bits 3-5 while the handler
|
|
1071
|
+
runs, and the restore puts back what the main code had.
|
|
1072
|
+
|
|
1073
|
+
The NMI needs the same treatment because `SEI` does not mask it: the
|
|
1074
|
+
RESTORE key and CIA2 can raise one at any time. An NMI handler without
|
|
1075
|
+
the wrapper reads `$DD0D` from RAM, CIA2's flag stays set, `/NMI` never
|
|
1076
|
+
rises, and no later NMI, from CIA2 or RESTORE, can happen (pitfall
|
|
1077
|
+
`kernal_nmi_handler_runs_stop_check`). The recipe's broken build showed
|
|
1078
|
+
exactly one NMI in 115 million cycles.
|
|
1079
|
+
|
|
1080
|
+
A handler without the wrapper under `$34` fails at once: its `$D019`
|
|
1081
|
+
write goes to RAM, the VIC's interrupt stays asserted, and the handler
|
|
1082
|
+
re-enters as soon as it returns. Measured in VICE: the recipe's two
|
|
1083
|
+
raster handlers re-entered each other for 115 million cycles (PAL; 100
|
|
1084
|
+
million NTSC) and main code never completed a pass. This is the same
|
|
1085
|
+
mechanism as `irq_during_charen_window`, with RAM in place of the
|
|
1086
|
+
character ROM. The handler's other writes land in the RAM copy
|
|
1087
|
+
(`$D020`, `$D012`, `$D400` were all found there).
|
|
1088
|
+
|
|
1089
|
+
### Variations
|
|
1090
|
+
|
|
1091
|
+
**Save in zero page.** `LDA $01` / `STA save` / `LDA #$35` / `STA $01`
|
|
1092
|
+
and `LDA save` / `STA $01` cost 17 cycles, one fewer than the stack
|
|
1093
|
+
form (arithmetic from the instruction table). Each handler that can
|
|
1094
|
+
nest needs its own byte.
|
|
1095
|
+
|
|
1096
|
+
**Music code under I/O.** The wrapper assumes the handler and the
|
|
1097
|
+
player live outside `$D000-$DFFF`. A player placed under I/O cannot
|
|
1098
|
+
write the SID while its own code is visible. GoatTracker 2's
|
|
1099
|
+
"zeropage ghostregs" option has the player write to a zero-page copy
|
|
1100
|
+
of the SID registers, which a copy loop moves to `$D400` afterwards
|
|
1101
|
+
with I/O banked in (GoatTracker 2 readme, section 5.1, not measured
|
|
1102
|
+
here).
|
|
1103
|
+
|
|
1104
|
+
**Loaders that do it for you.** Sparkle's loader sets `$35` at each loader
|
|
1105
|
+
call and `$34` while a bundle is decrunched under I/O, and its manual requires
|
|
1106
|
+
every IRQ to save `$01`, set `$35` and restore it (Sparkle 3.4 manual,
|
|
1107
|
+
"Loading to the RAM under I/O registers", not measured here).
|
|
1108
|
+
|
|
1109
|
+
### Cycle budget
|
|
1110
|
+
|
|
1111
|
+
Measured in VICE x64sc 3.10 with the recipe below, PAL and NTSC alike:
|
|
1112
|
+
the wrapper costs 18 cycles per interrupt taken (`LDA zp` 3, `PHA` 3,
|
|
1113
|
+
`LDA #` 2, `STA zp` 3, `PLA` 4, `STA zp` 3, which is what the
|
|
1114
|
+
instruction table gives). The recipe's line-120 handler is 61 cycles
|
|
1115
|
+
with it and 43 without, each plus the 7-cycle interrupt sequence. The
|
|
1116
|
+
Cost line is one interrupt; a frame pays 18 for every IRQ and NMI it
|
|
1117
|
+
takes: 36 for a two-split raster chain, 36 plus 18 per NMI with a CIA2
|
|
1118
|
+
timer running; the recipe's own frame, 2 IRQs and up to 4 NMIs, is
|
|
1119
|
+
108 (arithmetic). The wrapper is 10 bytes per handler (arithmetic from
|
|
1120
|
+
the opcode sizes).
|
|
1121
|
+
|
|
1122
|
+
### Recipes
|
|
1123
|
+
|
|
1124
|
+
- `recipes/kickassembler/irq-owns-port.md` — main code copies 4 KB
|
|
1125
|
+
under I/O at `$34` and checks it by sum 32 times while two raster
|
|
1126
|
+
IRQs, a SID player and a CIA2 NMI keep running; handler cycles with
|
|
1127
|
+
and without the wrapper; two broken builds described.
|
|
1128
|
+
|
|
1129
|
+
### Sources
|
|
1130
|
+
|
|
1131
|
+
- Sparkle 3.4 user manual (Sparta), "Loading to the RAM under I/O
|
|
1132
|
+
registers ($D000-$DFFF)" and "Common issues" item 6:
|
|
1133
|
+
https://github.com/spartaomg/SparkleCPP
|
|
1134
|
+
- GoatTracker 2 readme, section 5.1 "Playroutine options", option
|
|
1135
|
+
"Use zeropage ghostregs":
|
|
1136
|
+
https://sourceforge.net/projects/goattracker2/
|
|
1137
|
+
|
|
1138
|
+
## reu_dma — REU DMA transfers, fixed-address fills and the $FF00 trigger
|
|
1139
|
+
|
|
1140
|
+
**Complexity:** medium
|
|
1141
|
+
**Region:** both
|
|
1142
|
+
**Uses registers:** DF00, DF01, DF02, DF03, DF04, DF05, DF06, DF07, DF08, DF09, DF0A
|
|
1143
|
+
|
|
1144
|
+
### Why
|
|
1145
|
+
|
|
1146
|
+
A 17xx RAM Expansion Unit (1700 128 KB, 1764 256 KB, 1750 512 KB, per
|
|
1147
|
+
codebase64) and its modern equivalents add banks of RAM the CPU cannot
|
|
1148
|
+
address, and a DMA controller that moves bytes between them and C64
|
|
1149
|
+
memory at one byte per cycle. A game uses it as a store for level data
|
|
1150
|
+
and graphics that would otherwise be loaded from disk, as a copier that
|
|
1151
|
+
beats any CPU loop (an unrolled `LDA abs` / `STA abs` is 8 cycles a
|
|
1152
|
+
byte, `speedcode_generation`), and as a filler: with one address held
|
|
1153
|
+
still, it fills a block from one byte or streams a block into one
|
|
1154
|
+
register. It is an optional extra. A program that uses it must detect
|
|
1155
|
+
it and must still run without it.
|
|
1156
|
+
|
|
1157
|
+
### How
|
|
1158
|
+
|
|
1159
|
+
The registers sit at `$DF00-$DF0A`, in the cartridge I/O-2 page. Bit
|
|
1160
|
+
meanings are from codebase64 `base:reu_programming`. The ones marked
|
|
1161
|
+
"measured" were exercised by the recipe in VICE x64sc 3.10; the rest are
|
|
1162
|
+
from that source only.
|
|
1163
|
+
|
|
1164
|
+
| Register | Meaning |
|
|
1165
|
+
|---|---|
|
|
1166
|
+
| `$DF00` status, read | bit 7 interrupt pending; bit 6 end of block (measured); bit 5 verify fault (measured); bit 4 size, set for 256 KB chips, so on a 1764 or 1750 (measured set with `-reusize 512`); bits 3-0 version (measured 0). Reading clears the fault bit (codebase64; not measured here). |
|
|
1167
|
+
| `$DF01` command | bit 7 execute; bit 5 autoload, which restores the address and length registers after the transfer; bit 4 set = start now, clear = wait for a write to `$FF00` (both measured); bits 1-0 type: `00` stash C64 to REU, `01` fetch REU to C64, `10` swap, `11` verify (all measured). Bits 6, 3, 2 reserved. |
|
|
1168
|
+
| `$DF02/$DF03` | C64 address, low/high (measured) |
|
|
1169
|
+
| `$DF04/$DF05/$DF06` | REU address low/high and bank; bank bits 2-0 for 512 KB (measured: bits 7-3 read back as 1 in VICE with 512 KB) |
|
|
1170
|
+
| `$DF07/$DF08` | length, low/high; 0 = 65,536 (measured) |
|
|
1171
|
+
| `$DF09` interrupt mask | bit 7 enable, bit 6 on end of block, bit 5 on verify error; bits 4-0 unused |
|
|
1172
|
+
| `$DF0A` address control | bit 7 holds the C64 address still, bit 6 the REU address (both measured); bits 5-0 unused |
|
|
1173
|
+
|
|
1174
|
+
1. Detect. Write a pattern to `$DF02-$DF08` and read it back. Compare
|
|
1175
|
+
`$DF06` on bits 2-0 only. With no REU the reads are open bus (the
|
|
1176
|
+
byte the VIC-II last fetched, `hardware/c64-registers-reference.md`)
|
|
1177
|
+
and the pattern does not come back: the recipe read all `$00` on PAL
|
|
1178
|
+
and `$00` with one `$FF` on NTSC. Another cartridge that decodes
|
|
1179
|
+
`$DF00` can pass this test (codebase64); knowing the cartridge is the
|
|
1180
|
+
caller's job.
|
|
1181
|
+
2. Load the C64 address, REU address and bank, length and `$DF0A`.
|
|
1182
|
+
Without autoload the registers are left past the end, so load all of
|
|
1183
|
+
them before each transfer.
|
|
1184
|
+
3. Write the command to `$DF01`: `$90` stash, `$91` fetch, `$92` swap,
|
|
1185
|
+
`$93` verify. The CPU is halted until the last byte has moved.
|
|
1186
|
+
4. For a verify, read `$DF00` first to clear it, then test bit 5 after.
|
|
1187
|
+
On a mismatch the verify stops, bit 6 stays clear, and `$DF02/$DF03`
|
|
1188
|
+
point one past the differing byte (measured).
|
|
1189
|
+
|
|
1190
|
+
To reach the RAM under I/O, arm the transfer with bit 4 clear (`$81` for
|
|
1191
|
+
a fetch) while I/O is visible, set `$01` = `$34`, and write to `$FF00`.
|
|
1192
|
+
The write starts the DMA, which sees the memory configuration that is
|
|
1193
|
+
in force, so it reaches the RAM at `$D000-$DFFF`. `LDA $FF00` / `STA
|
|
1194
|
+
$FF00` keeps the byte already in the RAM under the KERNAL. Keep
|
|
1195
|
+
interrupts masked while `$01` is `$34`, or give the handlers the
|
|
1196
|
+
`irq_owns_processor_port` wrapper.
|
|
1197
|
+
|
|
1198
|
+
### Why it works
|
|
1199
|
+
|
|
1200
|
+
The REU is a bus master. It pulls the CPU off the bus and drives the
|
|
1201
|
+
address and data lines itself, one access per cycle, so a stash, fetch
|
|
1202
|
+
or verify moves one byte per cycle and a swap, which reads and writes
|
|
1203
|
+
each C64 byte, one byte per two. The VIC-II keeps priority. On a badline
|
|
1204
|
+
and on a sprite's fetch cycles it takes the bus from the REU as it
|
|
1205
|
+
takes it from the CPU, so the transfer waits (pitfalls
|
|
1206
|
+
`badline_cycle_loss`, `vic_bus_takeover_on_dma`). The `$FF00` trigger
|
|
1207
|
+
exists because the store to `$DF01` needs I/O visible, while the
|
|
1208
|
+
transfer may need it hidden.
|
|
1209
|
+
|
|
1210
|
+
### Variations
|
|
1211
|
+
|
|
1212
|
+
- **Fill.** Stash one byte once. Then fetch with `$DF0A` = `$40` (REU
|
|
1213
|
+
address held): every C64 byte in the block gets that value. The recipe
|
|
1214
|
+
fills 1,000 bytes of colour RAM this way in 1,000 cycles.
|
|
1215
|
+
- **Stream to a register.** Fetch with `$DF0A` = `$80` (C64 address
|
|
1216
|
+
held) to write every REU byte to one address, such as `$D418` for a
|
|
1217
|
+
digi. The DMA runs flat out, one byte per cycle, so the samples do not
|
|
1218
|
+
come out at a controlled rate unless the transfer is split into short
|
|
1219
|
+
lengths and timed; this is not measured here. The recipe uses the
|
|
1220
|
+
same bit the other way round, stashing 65,536 reads of one byte.
|
|
1221
|
+
- **Level store.** Fetch a level's map, charset and sprites at level
|
|
1222
|
+
start: 16 KB is about 16,400 cycles blanked, under one PAL frame of
|
|
1223
|
+
19,656 (arithmetic from the measured one cycle per byte).
|
|
1224
|
+
- **Double buffer.** Fetch the next screen's 1,000 bytes in 1,008
|
|
1225
|
+
cycles; with the screen on, allow for the badlines it crosses.
|
|
1226
|
+
|
|
1227
|
+
### Cycle budget
|
|
1228
|
+
|
|
1229
|
+
Measured in VICE x64sc 3.10 with the recipe, PAL and NTSC. The store to
|
|
1230
|
+
`$DF01` is part of the count; after it a stash, fetch, fill or verify of
|
|
1231
|
+
n bytes takes n cycles and a swap 2n, with the screen blanked. A 1-byte
|
|
1232
|
+
stash came to 9 cycles including the 8-cycle `LDA abs` / `STA abs`.
|
|
1233
|
+
With the text screen on, a 65,536-byte stash took 69,414 cycles on PAL
|
|
1234
|
+
(1.05 per byte) and 69,844 on NTSC (1.06). With eight sprites on 21
|
|
1235
|
+
lines as well, it took 71,134 on PAL and 71,439 on NTSC. The cost of a
|
|
1236
|
+
transfer depends on its length and where in the frame it runs, so this
|
|
1237
|
+
page carries no Cost line: budget one cycle per byte blanked, and about 6 %
|
|
1238
|
+
(PAL) and 7 % (NTSC) more with the screen on across whole frames. A transfer that must
|
|
1239
|
+
fit a raster window should be timed in that window.
|
|
1240
|
+
|
|
1241
|
+
### Recipes
|
|
1242
|
+
|
|
1243
|
+
- `recipes/kickassembler/reu-dma.md` — detects the REU; stashes, fetches
|
|
1244
|
+
into screen RAM, fills colour RAM from one byte, swaps, verifies (and
|
|
1245
|
+
reads the fault bit on a changed byte), fetches under I/O with the
|
|
1246
|
+
`$FF00` trigger, and times each transfer blanked, with the screen on
|
|
1247
|
+
and with eight sprites on.
|
|
1248
|
+
|
|
1249
|
+
### Sources
|
|
1250
|
+
|
|
1251
|
+
- codebase64, "REU programming" (Richard Hable, Marko Mäkelä), register
|
|
1252
|
+
bits, transfer speed, `$FF00` use, detection and model sizes:
|
|
1253
|
+
https://codebase64.net/doku.php?id=base:reu_programming
|
|
1254
|
+
|
|
1255
|
+
## pucrunch_decruncher — Pucrunch: a small forward decruncher in the zero page, stack and input buffer
|
|
1256
|
+
|
|
1257
|
+
**Complexity:** low
|
|
1258
|
+
**Region:** both
|
|
1259
|
+
**Uses kernal:** (none)
|
|
1260
|
+
**Cost:** bytes_code=245, zp_bytes=11
|
|
1261
|
+
**Cost basis:** derived-listing
|
|
1262
|
+
|
|
1263
|
+
The Cost line is the default C64 decruncher's footprint as pucrunch prints
|
|
1264
|
+
it after a crunch (`$2d/$2e`, `$f7-$1b6` and `$200-$234`: 192 plus 53
|
|
1265
|
+
bytes of decruncher, and eleven bytes of zero page, nine of them `$F7` to
|
|
1266
|
+
`$FF` and two more at `$2D`/`$2E`, which the stub rewrites as BASIC's
|
|
1267
|
+
end-of-program pointer before it decrunches). The `-ffast` variant
|
|
1268
|
+
reports 268 bytes and the `-fshort` variant 225, and `-fshort` widens the
|
|
1269
|
+
low pair to `$2D` to `$30`. Decrunch time is in the cycle budget below; it
|
|
1270
|
+
is a one-off cost at start, so it is not on the line.
|
|
1271
|
+
|
|
1272
|
+
### Why
|
|
1273
|
+
|
|
1274
|
+
Pucrunch is Pasi 'Albert' Ojala's LZ77 plus RLE cruncher, first published
|
|
1275
|
+
in 1997. The compressor is a single C file (`pucrunch.c` with its
|
|
1276
|
+
generated `pucrunch.h`) that builds with `cc` on any host; the source's
|
|
1277
|
+
version string reads 1.14, dated 22 November 2008. The author's page says
|
|
1278
|
+
that the compressor has been under the GNU LGPL since December 2005, and
|
|
1279
|
+
that the decompression code is under the wxWindows Library Licence, which
|
|
1280
|
+
in short lets the binary decruncher travel with the crunched data. That
|
|
1281
|
+
makes it one of the few crunchers whose licence a shipped game does not
|
|
1282
|
+
have to think about. The decruncher is small, sits in memory the KERNAL
|
|
1283
|
+
does not need at start-up, and expands forwards, so a file crunched with
|
|
1284
|
+
it can start as low as `$0258` and reach `$FFFF`. Exomizer is smaller on
|
|
1285
|
+
output and, on the two inputs measured here, faster to decrunch as well;
|
|
1286
|
+
the case for pucrunch is the licence, the C64-side footprint and the
|
|
1287
|
+
one-file build, not the ratio.
|
|
1288
|
+
|
|
1289
|
+
Everything below marked "measured" was run on 2026-09-23 with pucrunch
|
|
1290
|
+
1.14 built here (`cc -O2 -o pucrunch pucrunch.c -lm`, Apple clang 21,
|
|
1291
|
+
macOS arm64; the `.h` must sit beside the `.c`) and Exomizer 3.1.3b0
|
|
1292
|
+
built the night before, in the windowless x64sc build of VICE 3.10, PAL.
|
|
1293
|
+
|
|
1294
|
+
### How
|
|
1295
|
+
|
|
1296
|
+
The self-extracting form is the default. Give the machine and the PRG:
|
|
1297
|
+
|
|
1298
|
+
```text
|
|
1299
|
+
pucrunch -c64 game.prg game-pu.prg # default decruncher
|
|
1300
|
+
pucrunch -c64 -ffast game.prg game-fast.prg # a longer, faster decruncher
|
|
1301
|
+
pucrunch -c64 -fshort game.prg game-tiny.prg # a shorter, slower one
|
|
1302
|
+
pucrunch -c64 -fdelta game.prg game-dl.prg # delta LZ77, helps ramps and tables
|
|
1303
|
+
pucrunch -flist # every decruncher it can emit
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
The output is a PRG at `$0801` with a one-line BASIC stub (`SYS 2061` in
|
|
1307
|
+
every run here) followed by the decruncher and the crunched stream. Run,
|
|
1308
|
+
the stub copies the decruncher into the zero page from `$F7` upward, the
|
|
1309
|
+
low part of the stack page and the system input buffer at `$0200`, moves
|
|
1310
|
+
the crunched stream up in memory so that its last byte sits a computed
|
|
1311
|
+
safety margin past the end of the original file, and expands the original
|
|
1312
|
+
from its own load address upwards, back over the memory the stub and the
|
|
1313
|
+
stream occupied. When it finishes it jumps to the execution address, which
|
|
1314
|
+
it takes from the input file's own SYS line; `x<addr>` overrides it,
|
|
1315
|
+
`l<addr>` overrides the load address, `i0` leaves interrupts off at the
|
|
1316
|
+
jump and `g<val>` sets the `$01` bank configuration the program starts
|
|
1317
|
+
under. `-fbasic` selects the decruncher meant for a BASIC program (not
|
|
1318
|
+
run here).
|
|
1319
|
+
|
|
1320
|
+
The raw form has no stub. `-c0` writes a stand-alone stream with a short
|
|
1321
|
+
header (18 bytes on the test file, load address and execution address
|
|
1322
|
+
inside it) for a decruncher you link yourself; `-d` marks the input as
|
|
1323
|
+
headerless data with no load address, and `-c64 -d` still emits a C64
|
|
1324
|
+
stub for it. The author's page publishes the decruncher source
|
|
1325
|
+
(`uncrunch.asm`, DASM-style conditional assembly with switches for the
|
|
1326
|
+
machine, the speed variant, delta and the wrap buffer), which is where a
|
|
1327
|
+
raw-stream caller starts; the page places the decompression code under
|
|
1328
|
+
the wxWindows licence. It is not reproduced here.
|
|
1329
|
+
|
|
1330
|
+
Two things the layout imposes. First, the span from `$00F7` to `$0258`
|
|
1331
|
+
is the decruncher's during expansion: zero-page variables from `$F7` up,
|
|
1332
|
+
the lower part of the stack page and the input buffer are overwritten,
|
|
1333
|
+
so a pointer kept in `$FB` to `$FE` across the SYS comes back changed.
|
|
1334
|
+
The stub also writes `$2D`/`$2E` (`$2D` to `$30` with `-fshort`) on its
|
|
1335
|
+
way in, so state kept there is lost as well.
|
|
1336
|
+
The author's page describes the stack use as the part BASIC is not using
|
|
1337
|
+
at the time; whether a return to BASIC survives was not measured here.
|
|
1338
|
+
Second, the original file's end plus the safety margin must fit below
|
|
1339
|
+
`$10000`; when it does not, the compressor switches to a wrap-buffer
|
|
1340
|
+
variant on its own, so a file that ends at `$FFFF` still crunches, and it
|
|
1341
|
+
says which memory the result uses on every run, in the line beginning
|
|
1342
|
+
`uses the memory`. Nothing below `$0258` can be the file's load address,
|
|
1343
|
+
and pucrunch refuses such a file with a message saying so.
|
|
1344
|
+
|
|
1345
|
+
### Why it works
|
|
1346
|
+
|
|
1347
|
+
The stream mixes two codings. Run-length coding replaces a repeated byte
|
|
1348
|
+
with a count and one byte; a ranked table of the most common run bytes,
|
|
1349
|
+
built by the compressor and shrunk to the values actually used, lets the
|
|
1350
|
+
frequent ones cost less. LZ77 replaces a string that already appeared in
|
|
1351
|
+
the output with an offset back into it and a length. Anything neither
|
|
1352
|
+
covers is a literal, and here is the trick that keeps the decruncher
|
|
1353
|
+
small: a literal carries no flag bit of its own. Instead a few of its top
|
|
1354
|
+
bits are compared with a running escape code; a literal that happens to
|
|
1355
|
+
begin with the escape is written with an extra escape marker, and the
|
|
1356
|
+
compressor picks the number of escape bits per file so that this happens
|
|
1357
|
+
rarely (four bits on the test file, seven escaped literals). Lengths and
|
|
1358
|
+
offsets are Elias gamma codes, short for small values. All three kinds
|
|
1359
|
+
are decoded by one loop that writes forwards, which is why the stream can
|
|
1360
|
+
be expanded in place from its original address up, with only a small
|
|
1361
|
+
margin for the escaped literals.
|
|
1362
|
+
|
|
1363
|
+
### Variations
|
|
1364
|
+
|
|
1365
|
+
**The raw decruncher in your own loader.** For a level file the game
|
|
1366
|
+
loads itself, crunch with `-c0` (or `-d` for data without a load
|
|
1367
|
+
address), put the stream where the loader leaves it and call the raw
|
|
1368
|
+
decruncher with the stream's address. The `$F7` to `$0258` span is then
|
|
1369
|
+
the decruncher's for the duration, as above, so the loader's own zero
|
|
1370
|
+
page must lie below `$F7` or be saved first. Not measured here: the raw
|
|
1371
|
+
decruncher was not assembled in this run, because the shipped source is
|
|
1372
|
+
DASM syntax and its licence forbids putting a copy on this page.
|
|
1373
|
+
|
|
1374
|
+
**Faster or smaller.** `-ffast` bought 16 % of decrunch time for 23
|
|
1375
|
+
bytes on the mixed test file; `-fshort` saved 24 bytes and cost 20 % more
|
|
1376
|
+
time. `-fdelta` halved the crunched size of the mixed file, whose ramp
|
|
1377
|
+
and sine table it suits, and gained nothing on the code file.
|
|
1378
|
+
|
|
1379
|
+
**Comparison with Exomizer, measured.** Two subjects, both KickAssembler
|
|
1380
|
+
PRGs at `$0801` with a SYS stub: a 4,519-byte file of code plus mixed
|
|
1381
|
+
filler (a 1 KB zero run, a byte ramp, a sine table, repeated text), and a
|
|
1382
|
+
4,231-byte file whose filler is 4 KB of KERNAL ROM copied in as data, so
|
|
1383
|
+
that it looks like machine code. Each crunched PRG was run on its own to
|
|
1384
|
+
a green border with the marker at `$02FF` set, then again under a small
|
|
1385
|
+
loader that copied it to `$0801`, started a 32-bit CIA2 timer cascade and
|
|
1386
|
+
jumped to the stub's SYS address; the subject reads the timer as its first
|
|
1387
|
+
act. The figure is SYS to entry, decruncher setup included, and two runs
|
|
1388
|
+
gave the same figure to the cycle (the loader alone measures 271 cycles
|
|
1389
|
+
on the first subject and 228 on the second). One difference between the
|
|
1390
|
+
rows: pucrunch's C64 stub runs under SEI from its first instruction, while
|
|
1391
|
+
Exomizer's `sfx sys` stub reports interrupts enabled on entry, during and
|
|
1392
|
+
on exit, and the loader has interrupts on when it jumps. Exomizer's
|
|
1393
|
+
figures therefore include the KERNAL's IRQ service for the frames the
|
|
1394
|
+
decrunch takes, and pucrunch's do not. That works against Exomizer, so
|
|
1395
|
+
the ordering stands, but the two columns are not like for like.
|
|
1396
|
+
|
|
1397
|
+
| Cruncher | Mixed file: bytes | Mixed file: cycles | Code file: bytes | Code file: cycles |
|
|
1398
|
+
|---|---|---|---|---|
|
|
1399
|
+
| none | 4,519 | 271 | 4,231 | 228 |
|
|
1400
|
+
| pucrunch default | 1,084 | 349,505 | 3,918 | 1,008,259 |
|
|
1401
|
+
| pucrunch `-ffast` | 1,107 | 292,113 | 3,941 | 969,912 |
|
|
1402
|
+
| pucrunch `-fshort` | 1,060 | 418,487 | 3,894 | 1,314,757 |
|
|
1403
|
+
| pucrunch `-fdelta` | 598 | 253,598 | 3,919 | 1,024,326 |
|
|
1404
|
+
| exomizer `sfx sys` | 1,103 | 189,276 | 3,772 | 590,882 |
|
|
1405
|
+
|
|
1406
|
+
Exomizer's stream was smaller on the code file and its decruncher faster
|
|
1407
|
+
on both; pucrunch's default was 19 bytes smaller than Exomizer on the
|
|
1408
|
+
mixed file. Per output byte that is about 77 cycles for pucrunch's
|
|
1409
|
+
default and 42 for Exomizer on the mixed file, 238 and 140 on the code
|
|
1410
|
+
file. A claim that pucrunch decrunches faster than Exomizer was not borne
|
|
1411
|
+
out by either input here. Exomizer's own figures and its `-P` flag rules
|
|
1412
|
+
are in `loaders-packers.md`, `exomizer_basics`.
|
|
1413
|
+
|
|
1414
|
+
**What the decruncher leaves behind.** After each sfx run the sixteen
|
|
1415
|
+
bytes from `$0808` were printed: they matched the original file, BASIC
|
|
1416
|
+
line terminator and padding included, for pucrunch's default and
|
|
1417
|
+
`-fshort` decrunchers and for Exomizer. The host-side `pucrunch -u` on an
|
|
1418
|
+
sfx file gave a file of the right length that differed from the input at
|
|
1419
|
+
1,067 of its 4,519 byte positions, the first at offset 11; 1,063 of them
|
|
1420
|
+
are a zero byte that came back as 1, the 1 KB zero run among them. `-u`
|
|
1421
|
+
on the `-c0`
|
|
1422
|
+
stream gave the input back byte for byte. The C64-side decruncher is the
|
|
1423
|
+
one that matters, and it was checked only at the sixteen bytes above. Not
|
|
1424
|
+
investigated further.
|
|
1425
|
+
|
|
1426
|
+
### Cycle budget
|
|
1427
|
+
|
|
1428
|
+
Measured, PAL, SYS to entry: 349,505 cycles (0.35 s) for 4,517 bytes of
|
|
1429
|
+
mixed data and 1,008,259 cycles (1.02 s) for 4,229 bytes of code with the
|
|
1430
|
+
default decruncher; `-ffast` 292,113 and 969,912. Code that crunches
|
|
1431
|
+
badly is slow to decrunch as well as large, because most of its bytes go
|
|
1432
|
+
through the literal path. Budget by the output's nature, not its size,
|
|
1433
|
+
and measure a real level file before promising a load time.
|
|
1434
|
+
|
|
1435
|
+
### Recipes
|
|
1436
|
+
|
|
1437
|
+
- No recipe yet. The recipe verifier assembles a page's listing and runs
|
|
1438
|
+
the PRG; it has no step for running a cruncher on the result, so a page
|
|
1439
|
+
whose pin is a crunched program cannot be verified as the gate stands.
|
|
1440
|
+
|
|
1441
|
+
### Sources
|
|
1442
|
+
|
|
1443
|
+
- Pasi 'Albert' Ojala, "Pucrunch: An Optimizing Hybrid LZ77 RLE Data
|
|
1444
|
+
Compression Program" (the author's page, with `pucrunch.c`,
|
|
1445
|
+
`pucrunch.h` and `uncrunch.asm`), read for the memory layout, the
|
|
1446
|
+
escape scheme, the licence statements and the flag meanings:
|
|
1447
|
+
https://a1bert.kapsi.fi/Dev/pucrunch/
|
|
1448
|
+
- `pucrunch.c` version string `pucrunch 1.14 22-Nov-2008`; usage text
|
|
1449
|
+
from `pucrunch -h` run here.
|
|
1450
|
+
|
|
1451
|
+
## zx0_lzsa_decrunchers — ZX0, Dali, ZX02 and LZSA: modern crunchers with tiny decrunchers
|
|
1452
|
+
|
|
1453
|
+
**Complexity:** low
|
|
1454
|
+
**Region:** both
|
|
1455
|
+
**Uses kernal:** (none)
|
|
1456
|
+
**Requires:** ram_under_kernal
|
|
1457
|
+
**Cost:** bytes_code=257, zp_bytes=236
|
|
1458
|
+
**Cost basis:** arithmetic
|
|
1459
|
+
|
|
1460
|
+
The Cost line is Dali 0.3.5's standard self-extractor: 257 bytes of
|
|
1461
|
+
copier and decruncher, worked as the sfx file's size less its two-byte
|
|
1462
|
+
load address, its twelve-byte BASIC stub and the 770-byte stream the raw
|
|
1463
|
+
mode writes for the same input (269 with the stub, which the pucrunch
|
|
1464
|
+
section's 245 also leaves out), and the 236 bytes of zero page from `$01`
|
|
1465
|
+
upward that the copier fills (the `LDY #$EC` in the stub's own bytes). Dali saves that zero page
|
|
1466
|
+
on the stack and puts it back, so the figure is the space the decruncher
|
|
1467
|
+
borrows, not what it destroys. The `--small` self-extractor is 210 bytes
|
|
1468
|
+
and takes 183 bytes of zero page without saving them, and bitfire's own
|
|
1469
|
+
ZX0 self-extractor is 263 bytes over 212 bytes of zero page, also without
|
|
1470
|
+
saving. Decrunch times are in the cycle budget; they are a one-off cost at
|
|
1471
|
+
start, so they are not on the line.
|
|
1472
|
+
|
|
1473
|
+
### Why
|
|
1474
|
+
|
|
1475
|
+
Exomizer and pucrunch trade decruncher size for ratio and speed. A newer
|
|
1476
|
+
family of formats takes the other end of the trade: a decruncher of one
|
|
1477
|
+
to two hundred bytes, a handful of zero-page bytes, a decrunch loop with
|
|
1478
|
+
no tables to build, and a ratio that on the mixed test file below beats
|
|
1479
|
+
both of the older tools. Four of them have C64 or generic 6502
|
|
1480
|
+
decrunchers with a permissive licence, so a shipped game can carry the
|
|
1481
|
+
decruncher without thinking about it:
|
|
1482
|
+
|
|
1483
|
+
- **ZX0** by Einar Saukas, an optimal LZ77 cruncher whose repository
|
|
1484
|
+
holds the compressor and Z80 decrunchers, under the BSD 3-clause
|
|
1485
|
+
licence. It has no 6502 decruncher of its own; the README lists two
|
|
1486
|
+
6502 ports, one of them the copy inside bitfire.
|
|
1487
|
+
- **Dali** by Tobias Bindhammer (Bitbreaker), a C64 tool that re-encodes
|
|
1488
|
+
ZX0 output into its own bit layout and writes a C64 self-extracting
|
|
1489
|
+
PRG. Its compressor is Emmanuel Marty's Salvador, which produces
|
|
1490
|
+
ZX0-compatible streams. The tarball's assembly sources carry a BSD
|
|
1491
|
+
3-clause header; Salvador's own licence file is zlib with a CC0 match
|
|
1492
|
+
finder. The `dali.c` file has no licence header of its own; a shipped
|
|
1493
|
+
product should take the assembly headers as the statement.
|
|
1494
|
+
- **ZX02** by Daniel Serpell (DMSC), a ZX0 variant reworked for the 6502
|
|
1495
|
+
and not stream-compatible with ZX0, under the MIT licence. Its README
|
|
1496
|
+
names four 6502 decrunchers of 108 to 166 bytes, all using eight bytes
|
|
1497
|
+
of zero page.
|
|
1498
|
+
- **LZSA1 and LZSA2** by Emmanuel Marty, a byte-aligned format designed
|
|
1499
|
+
for 8-bit decoders, under the zlib licence with a CC0 match finder.
|
|
1500
|
+
The repository carries six generic 6502 decrunchers; the faster v1 and
|
|
1501
|
+
v2 sources state their own sizes (165 and 191 bytes for LZSA1, 241 and
|
|
1502
|
+
256 for LZSA2) and their zero-page use (the last seven bytes of the
|
|
1503
|
+
zero page for v1, the last eleven for v2); the fast sources state
|
|
1504
|
+
neither, and the small v2 source uses one zero-page byte, `$FC`.
|
|
1505
|
+
|
|
1506
|
+
The C64 self-extractors measured here were built from bitfire (commit
|
|
1507
|
+
`5a3964b`, 2026-09-10) and Dali 0.3.5 (the CSDb tarball). The 6502
|
|
1508
|
+
decrunchers for ZX02 and LZSA were read, not assembled; they are written
|
|
1509
|
+
for other assemblers, and their decrunch times were not measured here.
|
|
1510
|
+
|
|
1511
|
+
Everything below marked "measured" was run on 2026-09-23 in the
|
|
1512
|
+
windowless x64sc build of VICE 3.10, PAL, with the same two inputs, the
|
|
1513
|
+
same loader and the same method as the pucrunch section above.
|
|
1514
|
+
|
|
1515
|
+
### How
|
|
1516
|
+
|
|
1517
|
+
Each tool has a raw mode and, for two of them, a C64 self-extracting mode.
|
|
1518
|
+
The commands as used here:
|
|
1519
|
+
|
|
1520
|
+
```text
|
|
1521
|
+
zx0 game.bin game.zx0 # ZX0 v2.2, raw stream, no load address
|
|
1522
|
+
zx02 game.bin game.zx02 # ZX02, raw stream
|
|
1523
|
+
lzsa -f 1 -r game.bin game.lzsa1 # LZSA1 raw block
|
|
1524
|
+
lzsa -f 2 -r game.bin game.lzsa2 # LZSA2 raw block
|
|
1525
|
+
dali --sfx 0x0810 -o game-dali.prg game.prg # Dali C64 self-extractor, entry $0810
|
|
1526
|
+
dali --sfx 0x0810 --small -o game-tiny.prg game.prg
|
|
1527
|
+
dali -o game.dali game.prg # Dali raw stream for bitfire's decruncher
|
|
1528
|
+
zx0 --sfx 0x0810 -o game-bf.prg game.prg # bitfire's own ZX0 packer, same shape
|
|
1529
|
+
```
|
|
1530
|
+
|
|
1531
|
+
ZX0, ZX02 and LZSA read a headerless file and write a headerless stream;
|
|
1532
|
+
the caller's own decruncher knows where it goes. Dali and bitfire's
|
|
1533
|
+
packer read a PRG, keep its load address as the decrunch target, and
|
|
1534
|
+
report the original and packed spans on every run. Their `--sfx` takes
|
|
1535
|
+
the entry address as a number after the flag; give it, or the flag eats
|
|
1536
|
+
the next argument. Dali adds `--01` to set the processor port after
|
|
1537
|
+
decrunching, `--cli` to leave with interrupts on (the default is off),
|
|
1538
|
+
`--effect` for a border effect while it runs, `--no-inplace`,
|
|
1539
|
+
`--binfile`, `--from`/`--to` for a slice, `--prefix-file` for a
|
|
1540
|
+
dictionary already in memory, and `--relocate-sfx` for a stub without a
|
|
1541
|
+
BASIC line. bitfire's packer shares `--sfx`, `--no-inplace`,
|
|
1542
|
+
`--binfile`, `--from`/`--to` and the relocate flags, adds `-f` and `-q`,
|
|
1543
|
+
and uses `--use-prefix` in place of the prefix-file options; it has no
|
|
1544
|
+
`--01`, `--cli`, `--small` or `--effect`.
|
|
1545
|
+
|
|
1546
|
+
The self-extractor's layout, read from the two `sfx.asm` sources and
|
|
1547
|
+
checked against the bytes of the output: a one-line BASIC stub (`SYS
|
|
1548
|
+
2061` in every run here) followed by a copier, the decruncher and the
|
|
1549
|
+
stream. Run, the copier moves the decruncher into the zero page, counting
|
|
1550
|
+
down from `$EC` (Dali) or `$D4` (bitfire) to `$01`; the byte that lands
|
|
1551
|
+
at `$01` is the processor port's new value, `$34`, which banks the ROMs
|
|
1552
|
+
out so the whole 64 KB is writable. It then copies
|
|
1553
|
+
the crunched stream to the top of memory, ending at `$FFFF` under the
|
|
1554
|
+
KERNAL ROM, and decrunches forwards from the original load address,
|
|
1555
|
+
which is why both files here landed with their own BASIC stub back at
|
|
1556
|
+
`$0801` byte for byte. Dali's standard decruncher pushes the zero page
|
|
1557
|
+
onto the stack before overwriting it and pops it back on exit, with `$37`
|
|
1558
|
+
in the port unless `--01` says otherwise. Its `--small` decruncher and
|
|
1559
|
+
bitfire's do not save anything: measured here, the subject's KERNAL
|
|
1560
|
+
print path did not survive either. Under bitfire's self-extractor the
|
|
1561
|
+
subject reached its green border but printed nothing, and its timer
|
|
1562
|
+
bytes had to be read from memory with a monitor breakpoint; under Dali's
|
|
1563
|
+
`--small` it never reached the border at all, in a stand-alone run and
|
|
1564
|
+
under the loader alike. Which zero-page byte kills it was not traced;
|
|
1565
|
+
the KERNAL's own variables live in the span both overwrite. Code that
|
|
1566
|
+
follows a `--small` or bitfire decrunch must not call the KERNAL until it
|
|
1567
|
+
has reset what it needs, or must be a program that owns the machine.
|
|
1568
|
+
|
|
1569
|
+
The raw decrunchers ask for less. bitfire's `dzx0` uses five zero-page
|
|
1570
|
+
bytes at `$F8` to `$FC` and keeps its source pointer in its own operands;
|
|
1571
|
+
its header warns that it reads the unmodified ZX0 stream only, not the
|
|
1572
|
+
Dali one, which has its own `dzx0_dali.asm` with six bytes at `$F0`.
|
|
1573
|
+
ZX02's four decrunchers take eight bytes from `$80` by default and are
|
|
1574
|
+
ROM-able; the README names in-place decrunching with the compressor's
|
|
1575
|
+
reported `delta` (at worst 12 bytes per KB) as the safety margin. LZSA's
|
|
1576
|
+
small v1 decruncher keeps every pointer in self-modified operands and
|
|
1577
|
+
uses no zero page at all as written; small v2 uses `$FC`. None of the
|
|
1578
|
+
three formats carries a header that says which variant wrote it, so, as
|
|
1579
|
+
with Exomizer's `-P` bits, a stream and its decruncher must come from the
|
|
1580
|
+
same tool: ZX02 does not read ZX0, bitfire's decruncher does not read
|
|
1581
|
+
Dali, and LZSA1 and LZSA2 are different formats.
|
|
1582
|
+
|
|
1583
|
+
### Why it works
|
|
1584
|
+
|
|
1585
|
+
All four are LZ77: the output is built from literal bytes and from copies
|
|
1586
|
+
of what was already written, named by a distance back and a length. They
|
|
1587
|
+
differ in how those are coded. ZX0 keeps three kinds of block, literal
|
|
1588
|
+
run, match at the previous offset and match at a new offset, with lengths
|
|
1589
|
+
and offsets in interleaved Elias gamma codes, and the compressor picks
|
|
1590
|
+
the block sequence that is optimal for the whole file, which is where its
|
|
1591
|
+
ratio comes from. ZX02 caps the gamma codes at eight bits, stores offsets
|
|
1592
|
+
as positive values minus one and lets a match be one byte long, all so
|
|
1593
|
+
that an 8-bit register holds every quantity the decoder handles; that
|
|
1594
|
+
costs a little on long runs and gains on code. LZSA gives up the bit
|
|
1595
|
+
stream altogether: each token is a byte whose fields hold a literal count
|
|
1596
|
+
and a match length, with longer values in following bytes and the offset
|
|
1597
|
+
as one or two whole bytes, so the decoder never shifts a bit reservoir.
|
|
1598
|
+
LZSA2 adds nibble-sized fields and a repeat-offset match, which is why it
|
|
1599
|
+
beats LZSA1 on both inputs below. Dali re-encodes ZX0's blocks into the
|
|
1600
|
+
bit order bitfire's decruncher wants; the blocks themselves are ZX0's.
|
|
1601
|
+
|
|
1602
|
+
### Variations
|
|
1603
|
+
|
|
1604
|
+
**In-place decrunching.** Every tool here supports it. The stream is
|
|
1605
|
+
placed so that its end sits a small margin past the end of the output,
|
|
1606
|
+
and the decoder writes forwards without ever overtaking the input it has
|
|
1607
|
+
not read. ZX0 and ZX02 print the margin as `delta` when they crunch
|
|
1608
|
+
(3 bytes on the mixed file, 3 on the code file for ZX0); Dali and bitfire
|
|
1609
|
+
assume it unless `--no-inplace` is given. The C64 self-extractors go one
|
|
1610
|
+
further and copy the stream to the top of RAM first, so the margin is not
|
|
1611
|
+
the caller's problem.
|
|
1612
|
+
|
|
1613
|
+
**Streaming from disk.** bitfire is a disk loader whose files are all
|
|
1614
|
+
Dali-crunched, and it decrunches as sectors arrive; that path was not
|
|
1615
|
+
run here and its figures are bitfire's, not this page's. LZSA's raw block
|
|
1616
|
+
form and its stated small-decruncher sizes are the reason the format was
|
|
1617
|
+
built for that use on other 8-bit machines.
|
|
1618
|
+
|
|
1619
|
+
**Comparison, measured.** The same two subjects and loader as the
|
|
1620
|
+
pucrunch section: a 4,519-byte PRG of code plus mixed filler and a
|
|
1621
|
+
4,231-byte PRG whose filler is 4 KB of KERNAL ROM bytes. Sizes for the
|
|
1622
|
+
raw modes are the stream alone from a headerless input two bytes shorter;
|
|
1623
|
+
Dali's raw mode reads the PRG and writes a PRG, so its row is the stream
|
|
1624
|
+
after the two-byte load address it keeps (772 and 3,523 as files); sizes
|
|
1625
|
+
for the sfx modes are the whole PRG. Cycles are SYS to entry with
|
|
1626
|
+
the loader's own 271 or 228 cycles included, two runs each, identical to
|
|
1627
|
+
the cycle. The pucrunch and Exomizer rows are quoted from the pucrunch
|
|
1628
|
+
section above. One difference between the rows, as there: Dali's
|
|
1629
|
+
self-extractor runs under SEI from its first instruction; so does
|
|
1630
|
+
bitfire's.
|
|
1631
|
+
|
|
1632
|
+
| Cruncher | Mixed file: bytes | Mixed file: cycles | Code file: bytes | Code file: cycles |
|
|
1633
|
+
|---|---|---|---|---|
|
|
1634
|
+
| none | 4,519 | 271 | 4,231 | 228 |
|
|
1635
|
+
| dali `--sfx` | 1,041 | 114,369 | 3,792 | 311,234 |
|
|
1636
|
+
| dali `--sfx --small` | 982 | not measured here | 3,733 | not measured here |
|
|
1637
|
+
| bitfire zx0 `--sfx` | 1,035 | 99,422 | 3,786 | 295,612 |
|
|
1638
|
+
| zx0 v2.2 raw | 771 | not measured here | 3,522 | not measured here |
|
|
1639
|
+
| dali raw (stream) | 770 | not measured here | 3,521 | not measured here |
|
|
1640
|
+
| zx02 raw | 797 | not measured here | 3,522 | not measured here |
|
|
1641
|
+
| lzsa1 raw | 785 | not measured here | 3,852 | not measured here |
|
|
1642
|
+
| lzsa2 raw | 781 | not measured here | 3,682 | not measured here |
|
|
1643
|
+
| pucrunch default (pucrunch section) | 1,084 | 349,505 | 3,918 | 1,008,259 |
|
|
1644
|
+
| exomizer `sfx sys` (pucrunch section) | 1,103 | 189,276 | 3,772 | 590,882 |
|
|
1645
|
+
|
|
1646
|
+
Both ZX0 self-extractors are smaller than pucrunch's and Exomizer's on
|
|
1647
|
+
the mixed file and decrunch it in a third of pucrunch's time and well
|
|
1648
|
+
under Exomizer's; on the code file they are 20 bytes larger than
|
|
1649
|
+
Exomizer's sfx and about half its time. Per output byte that is about
|
|
1650
|
+
25 cycles for Dali and 22 for bitfire on the mixed file, 74 and 70 on the
|
|
1651
|
+
code file. The `--small` variant saved 59 bytes of PRG; its time is not
|
|
1652
|
+
on the table because the subject did not run to its entry under it.
|
|
1653
|
+
Raw-mode times are not measured here: the ZX02 and LZSA 6502 sources are
|
|
1654
|
+
written for other assemblers and were not ported for this run.
|
|
1655
|
+
|
|
1656
|
+
### Cycle budget
|
|
1657
|
+
|
|
1658
|
+
Measured, PAL, SYS to entry: 114,369 cycles (0.12 s) for 4,517 bytes of
|
|
1659
|
+
mixed data and 311,234 cycles (0.32 s) for 4,229 bytes of code with
|
|
1660
|
+
Dali's standard self-extractor; bitfire's 99,422 and 295,612. As with the
|
|
1661
|
+
older tools, code that crunches badly costs more per byte to decrunch:
|
|
1662
|
+
about three times as much per byte on both families, from a base a
|
|
1663
|
+
third as high here. Measure a real level file before promising a load
|
|
1664
|
+
time.
|
|
1665
|
+
|
|
1666
|
+
### Recipes
|
|
1667
|
+
|
|
1668
|
+
- No recipe yet. The recipe verifier assembles a page's listing and runs
|
|
1669
|
+
the PRG; it has no step for running a cruncher on the result, so a page
|
|
1670
|
+
whose pin is a crunched program cannot be verified as the gate stands.
|
|
1671
|
+
|
|
1672
|
+
### Sources
|
|
1673
|
+
|
|
1674
|
+
- Einar Saukas, ZX0 repository (`src/zx0.c` banner `ZX0 v2.2`, README's
|
|
1675
|
+
list of 6502 ports, `LICENSE`): https://github.com/einar-saukas/ZX0
|
|
1676
|
+
- Tobias Bindhammer, bitfire repository, `packer/zx0/` (`zx0.c` usage,
|
|
1677
|
+
`sfx.asm`, `6502/dzx0_v2.asm`, `LICENSE`): https://github.com/bboxy/bitfire
|
|
1678
|
+
- Tobias Bindhammer, Dali 0.3.5 (`dali035.tar.gz` from CSDb release
|
|
1679
|
+
247483: `dali.c` usage, `sfx.asm`, `dzx0_dali.asm`, `Makefile`,
|
|
1680
|
+
`salvador/README.md` and licence files): https://csdb.dk/release/?id=247483
|
|
1681
|
+
- Daniel Serpell, ZX02 repository (README's decruncher list and format
|
|
1682
|
+
notes, `6502/zx02-small.asm` and `zx02-optim.asm` headers, `LICENSE`):
|
|
1683
|
+
https://github.com/dmsc/zx02
|
|
1684
|
+
- Emmanuel Marty, LZSA repository (`src/lzsa.c` version string 1.4.1,
|
|
1685
|
+
README's licence section, `asm/6502/` headers): https://github.com/emmanuel-marty/lzsa
|
|
1686
|
+
- Usage text from each tool run here without arguments.
|