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,768 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: loader
|
|
3
|
+
chip: 6510
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- doc-type: technique-reference -->
|
|
7
|
+
|
|
8
|
+
# Loaders, Packers, and Disk Tricks
|
|
9
|
+
|
|
10
|
+
The C64's standard KERNAL LOAD routine, entering via the jump table at `$FFD5`, delivers around 300-400 bytes per second over the IEC serial bus. That ceiling shaped the entire culture of C64 software distribution: games shipped on multiple disks, each side holding one or two parts of a game; demos were structured as multi-part shows with a loading screen between parts; and a cottage industry of fast-loader technology emerged almost immediately after the machine launched in 1982. By the late 1980s, fast loaders delivering 20 times the KERNAL throughput had become the standard assumption for any serious demoscene production.
|
|
11
|
+
|
|
12
|
+
Packing runs parallel to loading. A crunched PRG that expands in place occupies less disk space (fitting more onto a side), loads faster (fewer bytes to transfer even before decompression), and presents a smaller RAM footprint before decompression begins. The interplay between loading speed and decompression time is the central engineering trade-off in this area: a very fast loader with no packing may wait on disk; an extremely compact packer with a slow depacker may erase the transfer savings.
|
|
13
|
+
|
|
14
|
+
This document covers the demoscene-standard fast loaders and packers in operational order — how to adopt them, how they work internally at the IEC and GCR level, and how to combine them into multi-part productions that stream content without visible loading pauses.
|
|
15
|
+
|
|
16
|
+
For the IEC bus hardware details underlying all fast-loader operation, see `../formats/iec-disk-reference.md`. For KERNAL jump-table entries (`LOAD`, `SAVE`, `CHKIN`, `CHKOUT`, `OPEN`, `CLOSE`, `CLALL`, `SETLFS`, `SETNAM`), see `../hardware/kernal-routines-reference.md`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## krill_loader_integration — Adopting Krill's fast loader
|
|
21
|
+
|
|
22
|
+
**Complexity:** high
|
|
23
|
+
**Region:** both
|
|
24
|
+
**Uses registers:** DD00
|
|
25
|
+
**Uses kernal:** CLALL, SETNAM, SETLFS, OPEN, CLOSE, CHKIN, CHKOUT, CHRIN, CHROUT, CLRCHN, READST, LISTEN, SECOND, IECOUT, UNLSN, TKSA
|
|
26
|
+
**Demands:** serial_bus_exclusive
|
|
27
|
+
**Claims:** serial_bus (owns), zero_page $E0-$EF (owns, relocatable), cia2_vic_bank (shares)
|
|
28
|
+
**Claims basis:** estimated
|
|
29
|
+
|
|
30
|
+
### Why
|
|
31
|
+
|
|
32
|
+
The KERNAL LOAD path at `$FFD5` is rate-limited by the 1541's software serial protocol, which delivers roughly 300-400 bytes per second in practice. Loading a packed 50 KB part takes around 130 seconds at KERNAL speed — more than two minutes. Krill's fast loader (by Krill of Plush, active development since around 2010 with frequent revisions through 2024) achieves about 7 kB/s typical and 7.7 kB/s peak on a 1541 (the v194 README's figures; an earlier version of this page said 7,500-7,800 B/s), a roughly 18-20x improvement over the KERNAL. At that speed, the same 50 KB loads in about seven seconds. The difference is the boundary between a production that feels responsive and one that tries the audience's patience.
|
|
33
|
+
|
|
34
|
+
### How
|
|
35
|
+
|
|
36
|
+
Krill uses a parallel protocol: instead of the KERNAL's bit-banged serial handshake driven by software on the C64 side, it loads custom 6502 machine code into the 1541 drive's RAM via the drive's memory-write (`M-W`) command followed by a memory-execute (`M-E`) command. The drive-side code replaces the 1541's normal serial-protocol loop with a tighter loop that drives the IEC lines directly through VIA1 port B in a tight handshake loop (the VIA shift register is used for GCR reading from the disk, not for the bus transfer — an earlier version of this page said the transfer used it). On the C64 side the program calls the resident's own entry points (`loadraw`, `loadcompd`) directly: Krill v194 does not hook the KERNAL LOAD vector at `$0330`/`$0331` and its source contains no write to it — that is how the classic cartridge fastloaders work, see `pitfalls/loader.md` (`fastloader_kernal_dependency`). An earlier version of this paragraph said Krill patched `$0330` so that `JSR $FFD5` took the fast path.
|
|
37
|
+
|
|
38
|
+
The installation sequence is:
|
|
39
|
+
|
|
40
|
+
1. Open a command channel to drive 8 (or whichever device number): `SETLFS 15, 8, 15`, `SETNAM ""`, `OPEN`. See `../hardware/kernal-routines-reference.md` for the SETLFS/SETNAM/OPEN sequence.
|
|
41
|
+
2. Send the drive-side code via `M-W` commands over the command channel. Krill's distribution includes a pre-assembled drive binary; the installer sends it in 35-byte `M-W` blocks (35 is the DOS maximum per command — v194 `src/install.s` compares against `#35`; an earlier version of this page said 32).
|
|
42
|
+
3. Send `M-E $0500` (or whatever address the drive code was loaded to) via the command channel. The 1541 begins executing the receiver loop.
|
|
43
|
+
4. The C64-side stub (a few hundred bytes, typically placed at a known spare area like `$0200` or tacked above the BASIC program area) initializes its state and signals the drive that the handshake is ready; it does not touch `$0330`/`$0331` (an earlier version of this step said it installed itself there).
|
|
44
|
+
5. Loads are made by calling the resident directly — `loadraw` for a raw file, `loadcompd` for a crunched one, filename pointer in X/Y (see the v194 reference below). `JSR $FFD5` is not accelerated, and while the drive is in loader mode every KERNAL serial call (`$FFD5`, `krnio`, `CHKIN`/`CHKOUT` on device 8) stalls until `uninstall` returns the drive to DOS. An earlier version of this step said all subsequent `JSR $FFD5` calls took the fast protocol and that `CHKIN`/`CHKOUT` kept working.
|
|
45
|
+
|
|
46
|
+
KERNAL calls, from the v194 source (`src/install.s` and `src/resident.s`, grepped here). `install` calls them at install time: CLALL, SETNAM, SETLFS, OPEN, CHKOUT, READST, CHKIN, CHRIN, CLRCHN and CLOSE to find the first drive present and read its error channel; LISTEN, SECOND, IECOUT (the source's `CIOUT`) and UNLSN to send drive commands, the `M-W` and `M-E` blocks among them; CHROUT to print an error or a warning. The resident calls the KERNAL only when built with `LOAD_VIA_KERNAL_FALLBACK`, and then only after the drive code failed to install: SETLFS, SETNAM, OPEN, CHKIN, CHRIN, CLRCHN, CLOSE and CLALL, plus SECOND and TKSA under `KERNAL_FALLBACK_OPEN_SEI_WORKAROUNDS` (CHKOUT and CHROUT there are C128-only). `loadraw` and `loadcompd` call none. No file calls LOAD (`$FFD5`); an earlier version of the `**Uses kernal:**` line above said LOAD, CHKIN, CHKOUT.
|
|
47
|
+
|
|
48
|
+
Krill v194 patches no KERNAL vector: the program calls `loadraw`/`loadcompd` directly, so the KERNAL's own file state (logical file numbers, SETLFS/SETNAM bookkeeping) is simply left alone (an earlier version of this sentence said a patched vector intercepted the KERNAL jump table's LOAD entry; the v194 source hooks no vector, as the note below says). (An earlier version of this page described a `KRILL_OPEN_CHANNEL` build option for servicing `OPEN`/`CHKIN` channels; no such option exists in v194, which exposes only `loadraw`/`loadcompd` and does not service KERNAL logical-file channels — see the v194 note below.)
|
|
49
|
+
|
|
50
|
+
### Why it works
|
|
51
|
+
|
|
52
|
+
The 1541's standard serial protocol is slow because it was designed for robustness on a bus shared with printers and other peripherals, and because Commodore's engineers optimized for component cost rather than speed. The tight software loop in the KERNAL's `IECIN`/`IECOUT` routines introduces handshake overhead for every bit. Krill's drive-side code takes full control of the 1541's 6502 and VIA chip, eliminating that overhead. The transfer sends two bits per handshake, one on CLK and one on DATA, with ATN as the clock: it exploits the ATN line (CIA2 `$DD00` bit 3 on the C64 side, VIA pin on the drive side) as the out-of-band handshake signal. On C64 hardware the IEC lines are CIA2 `$DD00` bits 3-7: ATN OUT bit 3, CLK OUT bit 4, DATA OUT bit 5, CLK IN bit 6, DATA IN bit 7 — the C64-side stub drives and samples those bits directly in the tight loop, bypassing the KERNAL's slower bit-banging. (Bit 2 is the RS-232 TXD output and is not part of the IEC bus; an earlier version of this page put ATN on bit 4 and CLK/DATA on bits 4, 3 and 2, and spoke of the VIA clocking "multiple bits per cycle". See `../hardware/cia-reference.md` §`$DD00`.)
|
|
53
|
+
|
|
54
|
+
NTSC compatibility: Krill includes an NTSC-compatible build option (the `NTSC_COMPATIBILITY` define in `loaderconfig.inc` for the cc65 build — **not** a KickAssembler `-DNTSC=1` flag; older KB text was wrong about the toolchain) because the timing loop on the C64 side is tuned to clock counts. NTSC's slightly different system clock (1.022 MHz vs PAL's 0.985 MHz) shifts the timing window slightly. Enabling `NTSC_COMPATIBILITY` makes a single resident binary run on both PAL and NTSC at a slight PAL-speed cost. Important: **PAL vs NTSC is not auto-detected by the install routine**, and no error is returned when running a PAL-only build on NTSC — for maximum speed you detect the machine yourself and select either the PAL-only or the NTSC-compatible resident. Using a PAL-only build on NTSC hardware typically causes load corruption or hangs.
|
|
55
|
+
|
|
56
|
+
### Variations
|
|
57
|
+
|
|
58
|
+
**Integrated decruncher (loadcompd).** Krill loads and depacks compressed files on the fly via the `loadcompd` entry point (enabled with `LOAD_COMPD_API` + a non-NONE `DECOMPRESSOR` in `loaderconfig.inc`), enabling load-and-decrunch pipelines that eliminate a separate decompression pass. The bundled decompressors live in `loader/tools/` and are selected by the `DECOMPRESSOR` config define — TSCrunch and ZX0 are "strongly recommended for demos", BITNAX / LZSA2 / TinyCrunch are recommended, and Exomizer / ByteBoozer2 / Doynax / NuCrunch / PuCrunch / SubSizer are also available (see `exomizer_basics` and `byteboozer_packer`).
|
|
59
|
+
|
|
60
|
+
**Multiple drive support.** Krill can be configured to install on drives at device addresses 8 through 11. On a multi-drive system, install separate drive stubs; the C64-side dispatch table selects the drive by device number before each LOAD call.
|
|
61
|
+
|
|
62
|
+
**Resetting the drive after use.** If the fast-loader stubs are in the 1541's RAM and the C64 resets, the drive stubs remain active and may interfere with a subsequent cold KERNAL LOAD attempt. A `UJ` (power-on reset: it jumps through the 1541's own reset vector, `$FFFC` -> `$EAA0`) or a bare `UI` (soft/warm reset via the vector at `$65`, default `$EB22`) sent over the command channel restarts the DOS and abandons RAM-resident code. `UI-` and `UI+` do not reset anything — they only set the drive's bus-timing flag at `$23` (`UI-` = VIC-20 timing, `UI+` = C64 timing) and return; an earlier version of this page named `UI-` as a reset. Krill also offers an `uninstall` entry point when built with `UNINSTALL_API`, which returns the drive to DOS cleanly without a reset.
|
|
63
|
+
|
|
64
|
+
### Cycle budget
|
|
65
|
+
|
|
66
|
+
Krill's 2-bit+ATN protocol moves each byte as four bit pairs on CLK/DATA, clocked by the host toggling ATN. Both ends are handshake loops of about 72 cycles per byte (v194 source: four 18-cycle phases on the C64 side, 69 cycles in the 1541's sendloop; the README states "72 cycles per byte"), which is a raw ceiling of roughly 13.7 kB/s; sector reads, GCR decoding, head stepping and per-block handshakes bring it down to the README's figures: 7.7 kB/s peak on a 1541, about 7 kB/s typical. The loading call blocks the mainline, but the receive loop leaves interrupts enabled and the drive simply waits on the handshake, so IRQ/NMI, sprites and badlines are allowed without restriction — music and IRQ-driven effects normally keep running while a part loads. (An earlier version of this page gave 17-22 C64 cycles and ~85 drive cycles per byte, 7,500-7,800 B/s, and said the CPU could run no other code during a load; the v194 README contradicts all three.) Sparkle (see `sparkle_irq_loader`) is the same kind of loader: 2-bit+ATN, 72 cycles a byte, blocking calls with interrupts free (an earlier version of this sentence said Sparkle loaded without a blocking mainline call; its manual says its calls block).
|
|
67
|
+
|
|
68
|
+
### v194 concrete integration reference (cc65 build)
|
|
69
|
+
|
|
70
|
+
Primary-source facts, verified against **Krill's Loader, repository version 194** (Plush, Nov 2022). This corrects/expands the generic description above.
|
|
71
|
+
|
|
72
|
+
**Provenance + distribution.** Krill's Loader is distributed as `loader-vNNN.zip` releases via CSDb (the "Repository Version NNN" releases). There is **no public git repository**; CSDb blocks automated downloads, so fetch it in a browser. The archive layout is `loader/{src,include,build,samples,docs,tools}` + `shared/`.
|
|
73
|
+
|
|
74
|
+
**Build system: cc65 — not KickAssembler or ACME.** The loader assembles with `ca65`/`ld65`/`ar65` plus GNU `make`, `gcc` (to build `cc1541` and the bundled compressors), and `perl` (symbol-file generation). **Use cc65 from git (V2.19+); the tagged 2.18 release (e.g. Homebrew's) silently miscompiles Krill v194 — the build is clean but the resident returns DEVICE_NOT_PRESENT / hangs on install.** Verified 2026-05-20: Homebrew cc65 2.18 produced a broken loader; cc65 git V2.19 (`cc3c40c`) produced a working one. (Also: `make-loadersymbolsinc.pl` uses `grep -P`, so on macOS put GNU grep ahead of BSD grep, or the symbol-file step errors.) `docs/Prerequisites.txt` lists the exact dependencies. Two integration modes, both driven by the `Makefile` in `loader/src/`:
|
|
75
|
+
|
|
76
|
+
- **`lib` target** → `build/loader-c64.lib`, linked directly into a **cc65** program by `ld65` via the `DISKIO`, `DISKIO_ZP`, and `DISKIO_INSTALL` segments (see `samples/cc65/`: `Makefile`, `Linkfile`, `c-program.c`). Use this only when your whole program is cc65.
|
|
77
|
+
- **`prg` target** → standalone binary blobs `build/install-c64.prg`, `build/loader-c64.prg`, and the symbol file `build/loadersymbols-c64.inc`. Built with `make PLATFORM=c64 prg INSTALL=<hexaddr> RESIDENT=<hexaddr> ZP=<hexbyte> [config defines]`. **This is the path for non-cc65 toolchains** (Oscar64, KickAssembler, raw asm): place the two blobs (after stripping each PRG's 2-byte load-address header) at the configured addresses and `JSR` the entry-point addresses from the symbol file. `samples/minexample/minexample.s` shows the blob-embedding pattern with `.incbin`.
|
|
78
|
+
|
|
79
|
+
**Memory footprint (default prebuilt config `INSTALL=1000 RESIDENT=0200 ZP=e0`).** Three relocatable pieces:
|
|
80
|
+
- *Installer* — transient, ~6.7 KB (`install` at `$1000`, code `$1000-$2B53`). Runs once to upload drive code + initialise the resident, then its RAM can be reused.
|
|
81
|
+
- *Resident loader* — tiny, ~520 B (`$0200-$0407`); holds the `loadraw`/`loadcompd` entry points. Must persist for all loads.
|
|
82
|
+
- *Zeropage* — a small window (default `$E0-$EF`; `loadaddrlo=$E0`, `loadaddrhi=$E1`).
|
|
83
|
+
|
|
84
|
+
All three are relocatable via the `INSTALL=`/`RESIDENT=`/`ZP=` make args. **Watch for zeropage collision** with a C compiler's own ZP register window — pick `ZP=` in a range the compiler leaves free.
|
|
85
|
+
|
|
86
|
+
**API (resident entry points — called directly, no `$FFD5` interposition required).** From `include/loader.inc`:
|
|
87
|
+
- `install` — KERNAL ROM must be enabled; it executes `CLI`. Out: `C`=set on error, `A`=status, `X`=drive type, `Y`=zp address of a version string. Call once at startup.
|
|
88
|
+
- `loadraw` — load a file without decompression. In: `X`/`Y` = lo/hi of a 0-terminated filename (an empty name loads the *next* file). If `LOAD_TO_API` is enabled, `C=0` loads to the file's stored address and `C=1` loads to a caller address in `loadaddrlo`/`loadaddrhi`. Out: `C`=error, `A`=status.
|
|
89
|
+
- `loadcompd` — load + depack (needs a `DECOMPRESSOR`).
|
|
90
|
+
- `fileexists`, `memdecomp`, `save` (`SAVE_OVERWRITE` — Krill **can** save, by overwriting an existing file of the same block size), `swapdrvcod` (run custom drive code), `uninstall` (if `UNINSTALL_API`). Status codes: `OK=$00`, `FILE_NOT_FOUND=$FF`, `DEVICE_NOT_PRESENT=$FE`, `GENERIC_KERNAL_ERROR=$FD`, `TOO_MANY_DEVICES=$FC`, `DEVICE_INCOMPATIBLE=$FB`.
|
|
91
|
+
|
|
92
|
+
Note: the documented usage calls `install`/`loadraw` **directly**, so the `$0330` vector-interposition model — which an earlier version of the How section above presented as Krill's mechanism — is not how v194 works at all: that concern (and the `fastloader_kernal_dependency` pitfall) applies to loaders that hook `$FFD5`. Krill's optional `LOAD_VIA_KERNAL_FALLBACK` mode does not hook it either; its fallback path calls `OPEN`/`CHKIN`/`BASIN` byte by byte and writes no vector.
|
|
93
|
+
|
|
94
|
+
**Raw-file load addressing.** Raw files are standard PRGs: `loadraw` consumes the first two bytes as the load address. To land a *header-less* data blob at a known buffer, build with `LOAD_TO_API=1` and set `loadaddrlo`/`loadaddrhi` with `C=1` — but the file must still carry a 2-byte prefix (those bytes are consumed even when the destination is overridden). Give author-time data files a 2-byte prefix.
|
|
95
|
+
|
|
96
|
+
**VIC-bank / bus-lock protocol (`$DD00` is shared between the VIC bank bits and the IEC bus).** `include/loader.inc` provides `SET_VIC_BANK` (A = VIC bank 0..3) to tell the loader the active bank, and `ENTER_BUS_LOCK` / `LEAVE_BUS_LOCK`. The rules (verified the hard way 2026-05-20):
|
|
97
|
+
- The loader does **not** magically preserve a non-default VIC bank by itself — a raw `loadraw` left `$DD00`'s bank bits changed across the load. You must tell it the bank via `SET_VIC_BANK`, or re-assert the bank yourself **between** loads.
|
|
98
|
+
- **Never raw-write `$DD00` while the loader is armed/resident.** Doing so corrupts its bus-lock state and the *next* drive op (load or uninstall) hangs. While the loader is idle you may write `$DD00` (e.g. to set the VIC bank), but a load must be entered through the loader's expectations (`LEAVE_BUS_LOCK` before / `ENTER_BUS_LOCK` after in the macro API). For a turn-based game that loads only at screen transitions, re-asserting the VIC bank as an idle write after each load (display paused) is the simplest safe pattern.
|
|
99
|
+
|
|
100
|
+
**Drive compatibility + fallback.** `ONLY_1541_AND_COMPATIBLE=1` shrinks install code by treating every drive as a 1541. `LOAD_VIA_KERNAL_FALLBACK=1` makes the loader fall back to the KERNAL load path when drive-code installation fails (incompatible drive such as SD2IEC, or true-drive emulation disabled) — directly mitigating `gcr_timing_assumes_stock_drive` at the cost of speed on those devices. Other tunables: `FILENAME_MAXLENGTH`, `DIRTRACK`/`DIRTRACK81` (shadow directory for dir-art), `FILE_EXISTS_API`, `UNINSTALL_API`, `LOAD_UNDER_D000_DFFF`, `END_ADDRESS_API` (progress displays).
|
|
101
|
+
|
|
102
|
+
**Emulator requirement.** Krill needs cycle-accurate **true-drive emulation** — in VICE keep true-drive enabled (the `x64sc` default — the monitor reports `Drive8TrueEmulation=1`); do **not** pass `+drive8truedrive` (per unit, 9-11 likewise; disabling it breaks the GCR protocol). An earlier version of this page named a `+truedrive` option, which `x64sc` rejects as unknown. See `../runtime/vice-reference.md`.
|
|
103
|
+
|
|
104
|
+
**Bundled tooling.** `loader/tools/` ships the compressors (exomizer-3.1, b2/ByteBoozer2, tscrunch, dali/zx0, bitnax, lzsa, pucrunch, nucrunch, subsizer, tinycrunch, doynamite, wcrush) and `cc1541` for building disk images.
|
|
105
|
+
|
|
106
|
+
**Embedding into an Oscar64 program (verified recipe, 2026-05-20).** Oscar64 cannot link the cc65 `.lib`, so use the `prg` target and embed the blobs:
|
|
107
|
+
- Build with git cc65: `make PLATFORM=c64 prg INSTALL=c000 RESIDENT=cd00 ZP=e0 EXTCONFIGPATH=<cfg> LOAD_RAW_API=1 LOAD_TO_API=1 NTSC_COMPATIBILITY=1 ONLY_1541_AND_COMPATIBLE=1 UNINSTALL_API=1`. Sizes: installer ~3.2 KB (transient), resident ~250 B.
|
|
108
|
+
- **Copy the resident after install, or put it OFF page 2.** Copying the resident to `$0200` *before* calling `install` lets install's KERNAL file-table writes (`$0259+`, made when it opens the drive command channel for the `M-W` upload) corrupt it, and the copy itself replaces the IRQ/BRK/NMI vectors at `$0314-$0319` with loader code; either copy after install with the CIA1 timer IRQ masked, or relocate (e.g. `INSTALL=$C000`, `RESIDENT=$CD00` in the free `$C000` block above the transient installer). `$0200` is Krill's own prebuilt default and the README requires only that a sub-`$0400` resident be copied *after* install — the hang an earlier version of this page attributed to page 2 itself is the ordering mistake. After install, the installer's RAM is reusable.
|
|
109
|
+
- Convert each blob to a C array (strip the 2-byte PRG header), `memcpy` to its origin at boot, then call the entry points via `__asm` (`jsr $C000` install, `jsr $CD00` loadraw with `X/Y`=filename ptr and `SEC`+`loadaddrlo/hi` for LOAD_TO). Disabling the CIA1 timer IRQ (`$DC0D=$7F`) is *not* required for install to succeed for a resident placed off page 2, but is good hygiene during cycle-exact transfers; a `$0200` resident destroys the `$0314` vector, so it is mandatory there.
|
|
110
|
+
- **Filename matching:** Krill compares the name bytes you pass against the directory entry. A file written by `c1541 -write host.bin name` (lowercase host arg) matches an **uppercase** ASCII request (`"TDLVL00"`, `0x54...`) — same convention as KERNAL `krnio`. A lowercase request gives FILE_NOT_FOUND.
|
|
111
|
+
- **Raw data files need a 2-byte prefix:** `loadraw` consumes the first two bytes of a raw file as a load address even with `LOAD_TO_API` overriding the destination. Prepend two dummy bytes to header-less data blobs at authoring time.
|
|
112
|
+
- **Save coexistence:** while the loader is resident the drive is in loader mode, so *all* KERNAL serial (`krnio` / `$FFD5`) stalls. To save via KERNAL: `uninstall` (drive returns to DOS) → KERNAL write → `install` again. Loads (incl. load-game) go through `loadraw`. Validated round-trip: install → loadraw → uninstall → krnio save+readback → reinstall → loadraw.
|
|
113
|
+
- **Keeping Krill resident is incompatible with `vic_setmode`/VIC-bank switches (important).** Any code that raw-writes `$DD00` — including Oscar64's `vic_setmode()`, which sets the VIC bank — corrupts the *armed* loader's bus-lock, hanging the next `loadraw`/`uninstall`. A game that switches VIC modes (e.g. a bitmap title ↔ text play screen) therefore should **not** keep the loader installed across those switches. The robust pattern (VICE-verified in Tideline) is **lazy install**: install only around each load batch (probe / level load / load-game), `uninstall` immediately after, then re-assert the VIC bank. With this model the drive sits in DOS by default, so **saves are plain `krnio` with no uninstall dance**, and there is no `$DD00` conflict because no `vic_setmode` runs inside the install→loads→uninstall window. Cost: one install (drive-code upload) per load batch — fine for infrequent loads; reconsider only if loads are very frequent.
|
|
114
|
+
|
|
115
|
+
### Recipes
|
|
116
|
+
|
|
117
|
+
- No recipe yet. (An earlier version of this page pointed at `recipes/kickassembler/cracktro-template.md`; that recipe is a single self-contained PRG with no loader, no disk access and `uses_kernal: []`. For the blob-embedding pattern see Krill's own `samples/minexample/minexample.s`, already cited above — external, not a recipe in this set.)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## wcf_packer — WCF (William Crucial Files) format compression
|
|
122
|
+
|
|
123
|
+
**Complexity:** medium
|
|
124
|
+
**Region:** both
|
|
125
|
+
**Uses kernal:** (none — runtime depacker is standalone)
|
|
126
|
+
|
|
127
|
+
### Why
|
|
128
|
+
|
|
129
|
+
WCF (William Crucial Files) is a classic C64 LZ-style packer from the early-to-mid 1990s. It uses a simple LZ77-style sliding-window encoder: the compressor searches a look-back buffer for matches to the current input sequence and emits either literal bytes or (offset, length) copy references. The runtime depacker is self-contained, requires no KERNAL calls, and runs entirely in C64 RAM. WCF does not achieve the compression ratios of Exomizer or ByteBoozer, and its depacker is larger than ByteBoozer's, but it remains relevant for legacy codebases that were built around it and for understanding the lineage of C64 packing tools.
|
|
130
|
+
|
|
131
|
+
**Legacy notice:** For new work, prefer Exomizer (see `exomizer_basics`) for best compression ratio or ByteBoozer (see `byteboozer_packer`) for smallest depacker. WCF is documented here for completeness and for reverse-engineering situations involving older cracker productions.
|
|
132
|
+
|
|
133
|
+
### How
|
|
134
|
+
|
|
135
|
+
The WCF workflow is offline:
|
|
136
|
+
|
|
137
|
+
1. Run the WCF compressor on the input PRG: `wcf input.prg output.wcf`. The output includes an embedded depacker stub.
|
|
138
|
+
2. The depacker stub is entered at the SYS target; it decompresses the payload in-place, expanding upward from the end of the packed data to the original load address. The expansion is back-to-front to avoid overwriting source bytes before they are consumed.
|
|
139
|
+
3. The resulting `.prg` file is loaded normally via KERNAL LOAD or a fast loader.
|
|
140
|
+
|
|
141
|
+
### Why it works
|
|
142
|
+
|
|
143
|
+
LZ77 compression works because real data (code and screen graphics especially) contains many repeated byte sequences. The encoder stores a single (offset, length) tuple instead of repeating the sequence, and the decoder reconstructs the original by copying from the already-decoded portion of the output buffer. The back-to-front depacking direction guarantees that the copy source is always in already-decoded space even when the output overwrites the packed input — a standard in-place decompression technique.
|
|
144
|
+
|
|
145
|
+
### Variations
|
|
146
|
+
|
|
147
|
+
**Standalone depacker.** The depacker can be extracted from the packed PRG and assembled independently if the target production has its own loader that handles the depacker setup. This is occasionally used in cracktro pipelines that chain multiple depackers.
|
|
148
|
+
|
|
149
|
+
### Cycle budget
|
|
150
|
+
|
|
151
|
+
Decompressing a 50 KB part takes on the order of seconds on PAL, not milliseconds: writing 50 KB with a bare `LDA (zp),Y` / `STA (zp),Y` copy loop alone costs about 820,000 cycles (0.83 s, measured in VICE with the screen blanked), and an LZ depacker adds bit-decoding on top of that — tens of cycles per output byte, so roughly 2-5 s depending on the data and the depacker (an estimate; no WCF binary is on this machine to time). An earlier version of this page gave 80-120 ms, which is under one CPU cycle per byte and impossible. The depacker size is approximately 150-200 bytes; specific figures depend on the version of the tool in use (no publicly canonical version number; tool was distributed through the warez scene with no formal release cycle).
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## exomizer_basics — Exomizer compression workflow
|
|
156
|
+
|
|
157
|
+
**Complexity:** low
|
|
158
|
+
**Region:** both
|
|
159
|
+
**Uses kernal:** (none)
|
|
160
|
+
|
|
161
|
+
### Why
|
|
162
|
+
|
|
163
|
+
Exomizer, written by Magnus Lind and actively maintained through version 3.1.2 (released 2021), is the de-facto standard C64 packer. It produces better compression ratios than WCF or ByteBoozer for typical C64 code and graphics, and its runtime depacker is both well-understood and small enough (~200 bytes for the `sys` variant) to embed in any production. The tool is cross-platform (runs on Linux, macOS, Windows), open-source, and its output is reproducible. When a C64 demoscene production needs to pack a part, Exomizer is the default choice unless depacker byte-count is the primary constraint.
|
|
164
|
+
|
|
165
|
+
### How
|
|
166
|
+
|
|
167
|
+
Offline compression produces a self-extracting PRG:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
exomizer sfx sys -o packed.prg unpacked.prg
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The `-o packed.prg` specifies the output; `unpacked.prg` is the source file. The `sfx sys` variant embeds a depacker that, on SYS, decompresses the payload and jumps to the original entry point. The depacker executes from a scratch area (default: `$0100` stack page, which Exomizer can use since the stack is not needed during decompression).
|
|
174
|
+
|
|
175
|
+
For raw streams without a SYS stub — used when Krill's `loadcompd` decrunches the file on the fly (built with the Exomizer decompressor selected) or when your own loader calls the depacker — use:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
exomizer raw -o packed.raw unpacked.prg
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The raw format carries no entry-point metadata; the calling code is responsible for setting up the depacker and passing the source and destination addresses.
|
|
182
|
+
|
|
183
|
+
**Which version these commands hold for.** All of them run unchanged on Exomizer 3; measured here with 3.1.3b0, built from the author's zlib-licensed source (Bitbucket commit `ba91318`, 2025-05-01) with `make` in `src/`. `sfx sys` on a 4,287-byte test PRG gave a 724-byte file that ran to a green border in the windowless x64sc. What changed in Exomizer 3.0.0 is the bit stream that `mem`, `raw` and `level` write, and a `-P<bitfield>` flag (0 to 63) that selects it: the three commands default to `-P39` since 3.1.0 (`-P7` in 3.0.x), and `-P0` writes the Exomizer 2 format. `sfx` needs no thought, since its stub is generated to match its payload; a `raw` or `mem` stream is only readable by a decruncher built for the same bits, so a decruncher from Exomizer 2, a tutorial's copy of it, or Exomizer's own two streaming decrunchers need `-P0` on the command line, and the shipped 3.x `exodecrunch` source needs its `#define`s to agree with any `-P+16` or `-P-32`. Measured: the shipped decruncher at its defaults reading a `-P0` stream of the same PRG overran its destination and dropped to `READY.`; with the matching flags it decrunched to the expected checksum. The bit meanings and the run table are in `../pitfalls/loader.md`, `exomizer3_proto_flags_mismatch`. An earlier version of this section gave the commands without saying which version they hold for or that `-P` exists.
|
|
184
|
+
|
|
185
|
+
**Level mode** (`exomizer level`) targets multi-file scenarios where individual files in a game level must be decompressed independently. Level mode trades some ratio for faster decompression because it avoids back-references that cross file boundaries.
|
|
186
|
+
|
|
187
|
+
### Why it works
|
|
188
|
+
|
|
189
|
+
Exomizer uses a variant of the LZMA algorithm adapted for 8-bit targets. It models the input with an LZ77 sliding-window matcher combined with a Huffman-like variable-length coding stage that assigns shorter codes to more frequent literals and match lengths. The result approaches 60-80% of original size for typical C64 machine code and 40-60% for graphics data (lower ratios for already-structured bitmap data). Decompression is slow relative to a plain copy: roughly 75-110 cycles per output byte for the sfx decruncher (Exomizer 3.1's `exo31info.txt` tables), so a 50 KB block takes seconds, not milliseconds — see the cycle budget below. An earlier version of this page said 50-80 ms per 50 KB, which is about one CPU cycle per byte and impossible.
|
|
190
|
+
|
|
191
|
+
Exomizer's standard depacker (`exodecrunch.s`, and the sfx stub built from it) decrunches backwards: it writes output from the highest destination address down, reading the packed data from its end toward its start. This is what makes in-place expansion work — the packed buffer is placed so that its end sits at (or just above) the end of the destination range, and every packed byte is consumed before output reaches it. The `sfx sys` mode handles this layout automatically; in `raw` mode you must place the packed data accordingly (or leave the ranges non-overlapping). Forward decrunching (low to high) is an option, not the default: a forward decruncher contributed by Krill shipped with 2.0beta5 (2006), and since 3.1.0 (2020-12-22) it is a build switch in `exodecrunch.s` (`DECRUNCH_FORWARDS = 1`; the file defaults it to 0); the data must then be crunched with the matching forward option, since the stream direction is fixed at crunch time. An earlier version of this section said the default was forward.
|
|
192
|
+
|
|
193
|
+
### Variations
|
|
194
|
+
|
|
195
|
+
**Exomizer 3 vs Exomizer 2.** Version 3 (available from `https://bitbucket.org/magli143/exomizer`) introduced improved compression for small files and the explicit `level` and `raw` modes. Version 3.0.0 changed the crunched bit stream itself, for every one of `mem`, `raw` and `level`, and made the shape selectable with `-P`; `-P0` gives the Exomizer 2 stream (an earlier version of this sentence said only that "version 2 had a slightly different raw format"). If using Krill's built-in Exomizer decompression (`loadcompd`), verify which Exomizer version and flags the loader build expects — the Krill README specifies this per Krill release.
|
|
196
|
+
|
|
197
|
+
**Multiple input files.** Exomizer accepts several input files, but it does not chain them:
|
|
198
|
+
```
|
|
199
|
+
exomizer sfx sys -o part.prg code.prg gfx.prg music.prg
|
|
200
|
+
```
|
|
201
|
+
All inputs are loaded into one 64 KiB memory image in command-line order (a later file overwrites any earlier one at the same addresses), and that single image — from the lowest start to the highest end, gaps zero-filled — is crunched once and decrunched once, with one entry point (the SYS at the BASIC start, or the address given with `sfx <jmpaddress>`). The `sfx` usage text says so: "All infiles are merged into the outfile. They are loaded in the order given." This is for assembling one part from separate code, graphics and music files, not for running parts one after another; a multi-part production still needs a loader such as Krill with Exomizer `raw` streams. An earlier version of this page said the parts "decompress in sequence".
|
|
202
|
+
|
|
203
|
+
### Cycle budget
|
|
204
|
+
|
|
205
|
+
Depacker size: approximately 200 bytes for the `sfx sys` variant (Exomizer 3.1.2, measured from the default depacker template). Decompression time: roughly 75-110 cycles per output byte for the sfx decruncher and roughly 60-100 for the raw/memory decruncher (Exomizer 3.1's `exo31info.txt` tables; measured here at 45 KB in 3.37 M cycles with `exomizer desfx -S` and 3.4-3.8 M cycles end-to-end in VICE x64sc), so a 50 KB part takes about 4-6 seconds on PAL, not milliseconds. An earlier version of this page said 50-80 ms (50,000-80,000 cycles) per 50 KB, about 50x too fast. Time is data-dependent; highly compressible data (long matches) is at the low end.
|
|
206
|
+
|
|
207
|
+
### Recipes
|
|
208
|
+
|
|
209
|
+
- No recipe yet. (An earlier version of this page pointed at `recipes/kickassembler/cracktro-template.md`; that recipe is assembled to a plain PRG and is not packed with Exomizer, ByteBoozer or any cruncher.)
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## doynax_decruncher — Doynax LZ packer integration
|
|
214
|
+
|
|
215
|
+
**Complexity:** medium
|
|
216
|
+
**Region:** both
|
|
217
|
+
**Uses kernal:** (none)
|
|
218
|
+
|
|
219
|
+
### Why
|
|
220
|
+
|
|
221
|
+
Doynamite (by Doynax; the author's real name is not verifiable here and an earlier version's "Johan Forsberg" attribution is dropped) is a compact, fast LZ depacker of roughly 225-260 bytes: the regular `decrunch.asm` assembles to 259 bytes with ACME, the stripped "simple" variant to `$E1` = 225 bytes (as its readme states), and the self-extracting form measured 309 bytes of total sfx overhead — comparable to Exomizer's depacker, not half of it (an earlier version of this page said ~100 bytes). The trade-off against Exomizer is a modestly worse compression ratio for the same input data; typical code files compress to around 65-85% of original size versus Exomizer's 60-80%. The Doynax packer is a niche choice, valued for depack speed rather than depacker size; a 225-259-byte depacker cannot be the reason a 256-byte intro would use it.
|
|
222
|
+
|
|
223
|
+
### How
|
|
224
|
+
|
|
225
|
+
The Doynax packer toolchain workflow is similar to Exomizer:
|
|
226
|
+
|
|
227
|
+
1. Compress offline: `lz [-o out.lz] [--sfx addr | --raw | --level] [--binfile] in.prg` from the Doynamite 1.1 distribution bundled in Krill's `loader/tools/doynamite1.1/` (`lz.c` source, plus the depacker sources `decrunch.asm` and `sfx.asm` in ACME syntax). An earlier version of this page named a `doynax-pack` command, which does not exist.
|
|
228
|
+
2. Assemble the depacker with the packed data appended: the depacker is a subroutine that takes a source pointer in ZP and a destination pointer in ZP, expands the data, and returns with `RTS`. It is not position-independent — it self-modifies absolute pointer operands (`lz_sector_ptr1..3 = *+1`) and the shipped `sfx.asm` relocates it with `!pseudopc` — so assemble it for the address it will run at. Include it in your project's assembler source.
|
|
229
|
+
3. At runtime: store source and destination pointers, `JSR decrunch_entry`.
|
|
230
|
+
|
|
231
|
+
The depacker source is in the Doynamite 1.1 distribution shipped with Krill's loader (v194); an earlier version of this page pointed at a `doynax/doynax-lz` GitHub path, which is not verifiable from this machine.
|
|
232
|
+
|
|
233
|
+
### Why it works
|
|
234
|
+
|
|
235
|
+
Doynax uses a two-level LZ scheme: a literal run mode and a back-reference mode, with a simple 1-bit tag in the compressed stream indicating which mode each token uses. The tag-bit approach avoids Huffman-like code tables in the depacker, which is the primary reason the depacker is so compact — there is no code table to initialize or walk. The cost is that optimal encoding (choosing between literal and back-reference) is done entirely by the offline compressor with no adaptive coding, which limits the ratio on data with non-uniform symbol frequencies.
|
|
236
|
+
|
|
237
|
+
### Variations
|
|
238
|
+
|
|
239
|
+
**Integrate with Krill.** Krill v194 decrunches Doynamite streams on the fly through `loadcompd` when the Doynamite decompressor is selected in `loaderconfig.inc` (the same `DECOMPRESSOR` switch as the ByteBoozer 2 and Exomizer options); there is no post-load callback hook — an earlier version of this paragraph described one. The alternative is `loadraw` into a buffer followed by a `JSR` to the depacker with ZP pointers set to the loaded block: load, decompress in place, continue.
|
|
240
|
+
|
|
241
|
+
**Zero-page/stack-page depacker.** The shipped `sfx.asm` copies the ~227-byte depacker to `$00C2`, where it spans the top of zero page and the low stack page, freeing the `$0801` area for the payload. This is useful in productions where low-RAM real estate is fully allocated. (An earlier version of this page said a 100-byte position-independent depacker "fits comfortably in the stack page"; it is neither 100 bytes nor position-independent.)
|
|
242
|
+
|
|
243
|
+
### Cycle budget
|
|
244
|
+
|
|
245
|
+
Decompression time: decompressing a 50 KB part takes on the order of seconds on PAL, not milliseconds: writing 50 KB with a bare `LDA (zp),Y` / `STA (zp),Y` copy loop alone costs about 820,000 cycles (0.83 s, measured in VICE with the screen blanked), and an LZ depacker adds bit-decoding on top of that — tens of cycles per output byte, so roughly 2-5 s depending on the data and the depacker (an estimate; Doynamite's depacker was not timed here). Its per-token overhead is lower than Exomizer's, so it sits toward the fast end. An earlier version of this page gave 40-60 ms per 50 KB, which is under one CPU cycle per byte and impossible. Depacker size: 259 bytes for the regular `decrunch.asm`, 225 (`$E1`) for the "simple" variant (Doynamite 1.1 as shipped in Krill v194, assembled with ACME; an earlier version said 100-110).
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## sparkle_irq_loader — Sparkle: an interrupt-tolerant loader and disk builder
|
|
250
|
+
|
|
251
|
+
**Complexity:** high
|
|
252
|
+
**Region:** both
|
|
253
|
+
**Uses registers:** DD00, DD02
|
|
254
|
+
**Uses kernal:** (none)
|
|
255
|
+
**Demands:** serial_bus_exclusive, kernal_rom_out
|
|
256
|
+
|
|
257
|
+
(An earlier version of this section credited Sparkle to JackAsser and Hollowman, marked it PAL-only, and described a raster IRQ at line 0 that received bytes in the background at 2,500-3,000 B/s while the main program polled a flag, with double-buffered blocks and a pause mode; the Sparkle 3.4 user manual contradicts or does not describe all of it, and the section below is rewritten from that manual. Nothing in it was measured here.)
|
|
258
|
+
|
|
259
|
+
### Why
|
|
260
|
+
|
|
261
|
+
Sparkle is a disk builder and loader in one, by Sparta (OMG), released under the BSD 3-Clause licence. A PC tool reads a text script (`.sls`) that lists the files, packs them into bundles and writes a D64 with the loader installer on it (manual pp. 2-4). The C64 side is a small resident loader and depacker. Its loader calls block, like Krill's (manual p. 23: "Sparkle's loader calls ... are blocking calls"), but the transfer is "freely interruptible" (p. 3), so raster IRQs, music and effects keep running while a bundle loads. The manual calls it an "all-in-one" solution: the same tool handles compression, disk layout and multi-side productions (p. 3).
|
|
262
|
+
|
|
263
|
+
### How
|
|
264
|
+
|
|
265
|
+
1. **Build the disk.** List files in the script; each bundle is loaded by one call. Files bound for `$D000`-`$DFFF` are marked with `*` so the depacker switches `$01` for them (pp. 10, 13).
|
|
266
|
+
2. **Boot.** Every directory entry on a Sparkle disk loads the installer. It installs the C64 resident code and the drive code, sets the I flag, writes `$35` to `$01` (KERNAL and BASIC banked out), cuts the stack to its lower `$60` bytes, and loads the first bundle. It then jumps to the start address with IRQs still disabled; it changes no other vector and no VIC register (pp. 15-16).
|
|
267
|
+
3. **Load the next part.** `JSR Sparkle_LoadNext` (`$021C`) loads the next bundle in script order, with no parameters (p. 17). `LDA #index : JSR Sparkle_LoadA` (`$019C`) loads bundle `$00`-`$7F` by index; `$80`+side requests another disk side (pp. 17-18).
|
|
268
|
+
4. **Prefetch.** `JSR Sparkle_SendCmd` (`$017A`) with a bundle index makes the drive find the bundle's first sector without transferring it; `JSR Sparkle_LoadFetched` (`$019F`) then loads it. `SendCmd` with `A=$FF` resets the drive (p. 18).
|
|
269
|
+
5. **Between parts.** `Sparkle_IRQ` (`$0160`) is a fallback interrupt handler that saves `$01`, sets `$35`, acknowledges `$D019` and calls `Sparkle_IRQ_JSR` (`$016E`, initially pointing at an RTS; patch it to a music player). Its RTI is at `$0179` (pp. 19-20).
|
|
270
|
+
|
|
271
|
+
Resident memory (pp. 3, 16):
|
|
272
|
+
|
|
273
|
+
| Range | Contents |
|
|
274
|
+
|---|---|
|
|
275
|
+
| `$0100`-`$015F` | the stack, reduced from a full page; restore SP to `$5F`, never `$FF` (p. 30) |
|
|
276
|
+
| `$0160`-`$0179` | `Sparkle_IRQ` |
|
|
277
|
+
| `$017A`-`$019B` | `Sparkle_SendCmd` |
|
|
278
|
+
| `$019C`-`$019E` | `Sparkle_LoadA` |
|
|
279
|
+
| `$019F`-`$02FF` | `Sparkle_LoadNext` and the depacker |
|
|
280
|
+
| `$0300`-`$03FF` | loader buffer; holds the start of the next bundle between sequential calls, so leave it alone or make the next call indexed (p. 16) |
|
|
281
|
+
|
|
282
|
+
Zero page: three bytes, `$02`-`$04` by default, chosen in the script, and free to use between loader calls (pp. 3, 9). Do not load anything into pages 1-3 (p. 30).
|
|
283
|
+
|
|
284
|
+
Drives: "Tested on 1541, 1541-II, 1571, and Oceanic drives, compatible with the 1541 Ultimate family" (p. 3). PAL and NTSC are both supported, plugins included (p. 3).
|
|
285
|
+
|
|
286
|
+
### Why it works
|
|
287
|
+
|
|
288
|
+
The C64 talks to the drive through CIA2 `$DD00`, 2 bits at a time plus ATN, at 72 cycles a byte (p. 3). Because the C64 clocks each bit pair with ATN, an interrupt between pairs only delays the transfer; the drive waits. The manual states the result ("Transfer is freely interruptible", p. 3), not this reasoning, which is from `krill_loader_integration`'s description of the same protocol family (rung 4 here).
|
|
289
|
+
|
|
290
|
+
Two rules follow from sharing `$DD00` with the VIC bank bits:
|
|
291
|
+
|
|
292
|
+
- **VIC bank.** Do not write `$DD00`; the loader may read the change as a drive command and reset the drive. Select the bank with `LDA #$3C+bank : STA $DD02`, bank 0-3 (pp. 20-21; common issue 1, p. 29). Any value may go into `$DD02` between loader calls if `$3C`+bank is back before the next call (the "indirect bus lock", pp. 21-22).
|
|
293
|
+
- **Free `$DD00`.** Write `$03` (any value with bits 3-5 clear) to `$DD02` first; then any value may go into `$DD00`. Restore `$DD00` to `$38`, then `$DD02` to `$3C`+bank, in that order (the "direct bus lock", pp. 22-23).
|
|
294
|
+
|
|
295
|
+
`pitfalls/loader.md` `fastloader_dd00_write_corrupts_resident` compares these rules with Krill's and Bitfire's.
|
|
296
|
+
|
|
297
|
+
Interrupt handlers must cope with `$01`: each loader call writes `$35` and may switch to `$34` while it depacks under I/O. A handler that touches I/O saves `$01`, sets `$35` and restores it (p. 20). Nested IRQs must save A, X, Y and `$01` to different places, or the outer handler returns the inner one's values to the loader and it crashes (common issue 7, p. 30).
|
|
298
|
+
|
|
299
|
+
### Variations
|
|
300
|
+
|
|
301
|
+
**More stack.** `Config: noirq` in the script drops the fallback IRQ and frees `$0160`-`$0179` for the stack (SP `$79`); `Config: basic` also drops `SendCmd` and `LoadA`, leaving sequential loading only, and frees the stack up to `$019E` (SP `$9E`) (pp. 9, 16).
|
|
302
|
+
|
|
303
|
+
**Disk flip without blocking.** `SendCmd` with `$80`+side returns at once; the program then writes `$08` to `$DD00` (the manual's own sanctioned `$DD00` write) and polls `$DD00` for the drive's ready signal from the main loop or an IRQ (pp. 23-24).
|
|
304
|
+
|
|
305
|
+
**Plugins.** A hi-score saver overwrites pre-defined files, also freely interruptible; a custom drive-code plugin lets a part upload its own drive code and restore Sparkle afterwards (pp. 3, 25-28).
|
|
306
|
+
|
|
307
|
+
### Cycle budget
|
|
308
|
+
|
|
309
|
+
Transfer: 72 C64 cycles a byte (p. 3). At 985,248 cycles a second on PAL that is a ceiling of about 13.7 kB/s before sector reads, head steps and depacking (rung 3; the manual gives no bytes-per-second figure). The drive's GCR fetch, decode and verify loop is 124 cycles and tolerates 272-314 rpm (p. 3). Real disks can take up to one extra revolution (10 frames) per bundle, more on a checksum retry, so the manual advises a sync buffer of at least 10 frames and preferably 20 after a load (common issue 9, p. 30). No figure here was measured.
|
|
310
|
+
|
|
311
|
+
### Sources
|
|
312
|
+
|
|
313
|
+
- Sparkle 3.4 user manual, Sparta (OMG), `manual/` in https://github.com/spartaomg/SparkleCPP (page numbers above are the manual's).
|
|
314
|
+
- Licence: BSD 3-Clause, "Copyright (c) 2019-2026, Sparta/OMG", `LICENSE` in the same repository.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## byteboozer_packer — ByteBoozer compression specifics
|
|
319
|
+
|
|
320
|
+
**Complexity:** low
|
|
321
|
+
**Region:** both
|
|
322
|
+
**Uses kernal:** (none)
|
|
323
|
+
|
|
324
|
+
### Why
|
|
325
|
+
|
|
326
|
+
ByteBoozer 2 (by HCL, Booze Design; its decruncher source is headed "ByteBoozer Decruncher /HCL May.2003" and "B2 Decruncher December 2014") ships in Krill's `loader/tools/b2/`. The standalone decruncher `Decruncher.inc` assembles to 245 bytes including its 8-byte offset table. The tool's own executable form (`b2 -c 0801 file.prg`, which writes `file.prg.b2`) prepends a 213-byte block at `$0801`: a 12-byte BASIC line (`SYS 2061`), a 17-byte entry at `$080D` that banks out ROM (`LDA #$34` / `STA $01`) and copies 183 bytes to zero page `$0010`, and a zero-page-resident decruncher plus exit that restores `$01` to `$37` and JMPs to the start address. An earlier version of this page said the depacker was 85 bytes, dated B2 to 2016 and named a `byteboozer2` command; the measured b2 build from Krill v194 contradicts all three. The compression ratio is modestly worse than Exomizer — typical code files compress to around 65-80% of original size versus Exomizer's 60-75% for comparable input — and the depacker's speed, not its size, is its selling point: a ~200-byte depacker is not what 256-byte intros use.
|
|
327
|
+
|
|
328
|
+
### How
|
|
329
|
+
|
|
330
|
+
Offline workflow:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
b2 input.prg (raw; writes input.prg.b2)
|
|
334
|
+
b2 -c 0801 input.prg (runnable sfx form; writes input.prg.b2)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The usage string is `b2 [-[c|e|r] xxxx] <filename>`; output is always `<filename>.b2`, there is no separate output-file argument, and there is no `byteboozer2` command (an earlier version of this page gave `byteboozer2 input.prg output.b2`). The raw `.b2` is the packed data without a SYS stub. The `-c` form is a runnable PRG:
|
|
338
|
+
|
|
339
|
+
1. The BASIC stub occupies `$0801` in the standard layout: `10 SYS 2061` (12 bytes) followed by the machine-code entry at address 2061 (`$080D`).
|
|
340
|
+
2. The 17-byte entry at `$080D` banks out ROM (`LDA #$34` / `STA $01`), copies the 183-byte decruncher to zero page `$0010` and runs it there; it decompresses to the original load address.
|
|
341
|
+
3. After decompression, the exit restores `$01` to `$37` and JMPs to the original entry point.
|
|
342
|
+
|
|
343
|
+
The depacker source is `Decruncher.inc` in the b2 distribution (not `depack.asm`; 245 bytes assembled standalone including its 8-byte offset table). The packed data follows the 213-byte stub in the sfx PRG, or in raw form is loaded to a scratch area first and then decompressed.
|
|
344
|
+
|
|
345
|
+
### Why it works
|
|
346
|
+
|
|
347
|
+
ByteBoozer uses a proprietary LZ-style compression that prioritizes depacker simplicity over encoder complexity. The format uses a fixed token structure: each token is either a literal byte or a (distance, length) back-reference. The token header uses a 1-bit flag for literal vs reference, followed by either the literal value or encoded distance/length fields. No Huffman table and no dynamic coding means the depacker needs no initialization beyond setting the read pointer — it simply iterates tokens and writes output. The encoder runs an exhaustive brute-force match search offline to compensate for the format's limited flexibility, which is why encoding a large file takes longer than Exomizer but the depacker is drastically simpler.
|
|
348
|
+
|
|
349
|
+
### Variations
|
|
350
|
+
|
|
351
|
+
**ByteBoozer 1 vs ByteBoozer 2.** ByteBoozer 1 dates from May 2003 and ByteBoozer 2 from December 2014 (the dates in the decruncher source's own header; an earlier version of this page said 2004 and 2016, and gave an unverified ~120-byte B1 depacker size, dropped here). B2 revised the token format; the two formats are incompatible. All current scene usage targets ByteBoozer 2.
|
|
352
|
+
|
|
353
|
+
**Krill integration.** Krill decrunches ByteBoozer 2 data on the fly through its `loadcompd` entry point when built with `LOAD_COMPD_API` enabled and `DECOMPRESSOR = DECOMPRESSORS::BYTEBOOZER2` in `loaderconfig.inc` (v194); there is no separate post-load hook, and the `DECRUNCH_BYTEBOOZER2` option an earlier version of this page named does not exist in the v194 archive.
|
|
354
|
+
|
|
355
|
+
**Depacker placement.** The depacker can be placed on the zero page, in the stack page, or in the BASIC stub area; the b2 sfx form does run from zero page, but occupies ~184 bytes of it (`$0010-$00C6`), not 85. Zero-page placement does not make the depacker run faster: instruction fetches cost the same from any page, and the depacker's source/destination pointers and bit buffer are zero-page variables wherever the code itself sits (only a self-modifying depacker that reads or writes its own operand bytes with zero-page addressing would gain a cycle per such access). Measured: identical depacker-style code at `$0040` and `$0300` took 11,382 cycles each under VICE. An earlier version of this page claimed a speed gain from zero-page placement. The reason to put the depacker in zero page or the stack page is space: it frees the `$0801` area for the packed payload, as in the layout in `crunched_data_in_basic_stub` below.
|
|
356
|
+
|
|
357
|
+
### Cycle budget
|
|
358
|
+
|
|
359
|
+
Depacker size: 245 bytes standalone (`Decruncher.inc` with its 8-byte offset table), 213 bytes of sfx overhead in the `b2 -c` form (measured from the b2 build in Krill v194; an earlier version of this page said 85 bytes). Decompression time: decompressing a 50 KB part takes on the order of seconds on PAL, not milliseconds: writing 50 KB with a bare `LDA (zp),Y` / `STA (zp),Y` copy loop alone costs about 820,000 cycles (0.83 s, measured in VICE with the screen blanked), and an LZ depacker adds bit-decoding on top of that — tens of cycles per output byte, so roughly 2-5 s depending on the data and the depacker (an estimate; the b2 depacker was not timed here). It is faster than Exomizer for similar data because the simpler token format has lower per-token overhead, partially offset by the somewhat lower compression ratio meaning slightly more bytes to read. An earlier version of this page gave 30-50 ms per 50 KB, which is under one CPU cycle per byte and impossible. With a 213-byte sfx stub, a 256-byte PRG leaves 41 bytes of packed data, so a general-purpose packer is not what 256-byte intros use.
|
|
360
|
+
|
|
361
|
+
### Recipes
|
|
362
|
+
|
|
363
|
+
- No recipe yet. (An earlier version of this page pointed at `recipes/kickassembler/cracktro-template.md`; that recipe is assembled to a plain PRG and is not packed with Exomizer, ByteBoozer or any cruncher.)
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## disk_protection_tricks — GCR-level tricks for copy protection
|
|
368
|
+
|
|
369
|
+
**Complexity:** scene-tier
|
|
370
|
+
**Region:** both
|
|
371
|
+
**Uses registers:** DD00
|
|
372
|
+
**Uses kernal:** (none — KERNAL bypassed)
|
|
373
|
+
|
|
374
|
+
### Why
|
|
375
|
+
|
|
376
|
+
Commercial C64 software from approximately 1984 through 1995 was distributed on floppy disks with copy protection intended to prevent casual duplication. The protection mechanisms operated below the level of the DOS filesystem — at the GCR (Group Code Recording) encoding layer that the 1541 uses to store bits on the magnetic surface. Understanding these techniques matters today for two reasons: reverse-engineering preservation copies of commercial games requires recognizing and neutralizing protections, and the body of GCR-level technique that copy-protection authors developed represents some of the most creative exploitation of the 1541 hardware documented anywhere.
|
|
377
|
+
|
|
378
|
+
**Scope note:** New productions do not use copy protection. This section documents techniques for recognition and reverse-engineering purposes.
|
|
379
|
+
|
|
380
|
+
### How
|
|
381
|
+
|
|
382
|
+
The 1541 stores data using GCR encoding: each 4-bit nibble maps to a 5-bit GCR code, giving 10 bits on disk per byte. Sectors are framed by SYNC marks — sequences of ten or more consecutive 1 bits, which the 1541's bit-counter hardware recognizes as frame delimiters. Standard disk format uses fixed-length SYNC marks, predictable sector IDs, and a fixed number of sectors per track. Copy protection exploits every degree of freedom the hardware allows:
|
|
383
|
+
|
|
384
|
+
**Half-tracks.** The 1541's stepper motor moves the head in half-track increments (track 1.0, 1.5, 2.0, etc.) even though the standard DOS only steps in full-track increments. A protected disk can write data on half-track 17.5, which a standard DOS never reads. The protection scheme steps the head to 17.5 at startup, reads a signature byte, and verifies it before proceeding. A copy program that only reads full tracks produces a disk missing the half-track data, which fails the check.
|
|
385
|
+
|
|
386
|
+
**Track signatures and non-standard SYNC patterns.** The standard SYNC is ten or more consecutive 1 bits. A protected disk can write a custom SYNC-length signature: the write routine explicitly pulses the write gate to produce a SYNC of exactly 11 bits, or uses SYNC-like patterns between sectors that the standard DOS interprets as errors. The protection reader uses bit-level polling of the 1541's VIA shift register to detect the exact bit count, which a standard copy cannot reproduce because copy programs reset SYNC detection on the first SYNC edge.
|
|
387
|
+
|
|
388
|
+
**Variable sector counts.** A standard 1541 track holds 17-21 sectors depending on the track zone. A protection can format a track with 22 sectors by shortening the inter-sector gap, or with 13 sectors by lengthening it. The standard DOS fails to read a track with non-standard sector count because it expects a fixed count. The protection loader uses a custom sector scanner that finds sectors by SYNC polling rather than by count.
|
|
389
|
+
|
|
390
|
+
**Killer tracks.** Some protections deliberately write malformed GCR data on a track to cause the 1541's read PLA to lock up or the DOS to spin indefinitely looking for a valid sector header that never appears. The protection code times out and interprets the timeout as a "copy-not-present" signal. Killer tracks can damage real disk drives if the disk is inserted in a drive that repeatedly attempts to read the bad track; they are considered hostile by the preservation community.
|
|
391
|
+
|
|
392
|
+
**Bad-sector signatures.** A sector can be written with an intentionally wrong checksum byte. The standard DOS reports a read error on that sector. The protection's custom reader knows to expect the bad checksum and interprets it as the "original disk" signature. A copy program that tries to duplicate the bad sector writes a corrected-checksum copy, which the protection code rejects.
|
|
393
|
+
|
|
394
|
+
### Why it works
|
|
395
|
+
|
|
396
|
+
All of these tricks work because the 1541 provides raw access to the GCR bitstream via its VIA port and shift register if you bypass the DOS ROM. Custom loader code running in the 1541's RAM (installed via `M-W`/`M-E`, exactly as fast loaders do) can read raw GCR sequences, count SYNC widths, step to half-tracks, and tolerate checksum errors that the standard DOS treats as fatal. The C64-side protection code sends parameters to the drive-side code and receives a signed response that only the original disk geometry can produce.
|
|
397
|
+
|
|
398
|
+
### Variations
|
|
399
|
+
|
|
400
|
+
**Burst Nibbler / Maverick.** The most widely used copy tools of the era (Burst Nibbler, Maverick, Copy II PC, Final Cartridge) progressively learned to capture each new protection technique by reading raw GCR tracks and reproducing the exact bit patterns. The arms race between protection authors and copy-tool authors drove the GCR techniques to ever more exotic territory, culminating in protections that required specialized hardware or multi-pass reading to defeat.
|
|
401
|
+
|
|
402
|
+
**Preservation approach.** Modern preservation uses drive-introspection tools (notably Kryoflux, an open-source USB floppy controller that captures raw flux transitions) to create full magnetic images of disks, including all half-tracks and malformed sectors. VICE reads G64 (GCR bit-level) and P64 (flux-pulse-level) images, attached with `-8 image.g64` (or `-autostart`); true drive emulation, on by default, is required for them. It does not read raw KryoFlux stream or SCP dumps — convert to G64/P64 with the KryoFlux/nibtools/HxC tools first. There is no `-floppytype` option (an earlier version of this page named one; `x64sc -help` lists `-drive8type`, which only matters when a different drive mechanism is wanted — the default type reads G64/P64).
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## multi_load_sequencing — Designing demos around incremental load
|
|
407
|
+
|
|
408
|
+
**Complexity:** medium
|
|
409
|
+
**Region:** both
|
|
410
|
+
**Uses kernal:** (none — loads go through Krill's own entry points, not $FFD5; an earlier version of this line said LOAD, which made the compatibility check report a false serial-bus conflict with krill_loader_integration)
|
|
411
|
+
|
|
412
|
+
### Why
|
|
413
|
+
|
|
414
|
+
A C64 has 64 KB of RAM, of which roughly 38-40 KB is usable by a production after accounting for the video matrix, character ROM shadow, sprite data, SID, CIA and VIC-II register space, stack, zero page, and the BASIC/KERNAL ROM areas. A multi-part demo cannot hold all its parts in RAM simultaneously: a three-part demo with three distinct sets of graphics, music, and code easily requires 150-200 KB uncompressed. The solution is multi-load sequencing: each part loads the next part's data while (or before) transitioning out of the current part's effect. Done correctly, the audience perceives a continuous flowing production; done badly, a blank screen and a spinning drive indicator for thirty seconds between parts.
|
|
415
|
+
|
|
416
|
+
### How
|
|
417
|
+
|
|
418
|
+
The canonical multi-load memory layout dedicates a fixed "persistent zone" that survives across all parts:
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
$0000-$00FF Zero page — persistent (ZP variables used by loader + IRQ)
|
|
422
|
+
$0100-$01FF Stack — persistent (loader subroutine calls)
|
|
423
|
+
$0200-$0407 Krill resident (v194 prebuilt default RESIDENT=0200, 519 B with the
|
|
424
|
+
ZX0 loadcompd entry; a raw-load-only build is ~250 B, see the Oscar64
|
|
425
|
+
notes in krill_loader_integration). Copy it here only AFTER `install`
|
|
426
|
+
returns: install's KERNAL OPEN/CLOSE calls write the file tables at
|
|
427
|
+
$0259-$0276 and other lowmem. This placement also overwrites the
|
|
428
|
+
KERNAL RAM vectors at $0314-$0333, so either mask the CIA1 timer IRQ
|
|
429
|
+
($DC0D=$7F, as Krill's minexample does) or relocate with RESIDENT=
|
|
430
|
+
(Krill's own sample uses RESIDENT=$2000). An earlier version of this
|
|
431
|
+
layout gave the resident 256 bytes at $0200-$02FF and called
|
|
432
|
+
$0300-$03FF persistent vectors, which the default resident overwrites.
|
|
433
|
+
$0800-$0FFF Music data + SID player (persistent across parts if music loops)
|
|
434
|
+
$1000-$BFFF Part area (fully reclaimed between parts — code + graphics)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Transition procedure from Part N to Part N+1:
|
|
438
|
+
|
|
439
|
+
1. Part N queues its final effect (a fade, a wipe, a scroll-off) that takes approximately 5-10 seconds — enough time to load Part N+1's data.
|
|
440
|
+
2. Part N calls the Krill LOAD stub to begin loading `PART_N1.PRG` (or the packed `PART_N1.B2`/`PART_N1.EXO`) into the part area at `$1000`.
|
|
441
|
+
3. While the load proceeds, Part N continues its fade effect using only the persistent zone (music player, IRQ handler, border tweak). Code in the part area is finished and no longer called.
|
|
442
|
+
4. When the load completes (Krill's LOAD returns to the caller), the transition effect finishes its last frame.
|
|
443
|
+
5. Part N JMPs to `$1000` (or the actual entry point specified by the packed PRG's SYS target).
|
|
444
|
+
|
|
445
|
+
Sparkle (see `sparkle_irq_loader`) follows the same steps: its loader calls block too, and the fade keeps running from interrupts (an earlier version of this sentence said Sparkle loaded in the background so that steps 2-4 ran concurrently; its manual says its calls block).
|
|
446
|
+
|
|
447
|
+
### Why it works
|
|
448
|
+
|
|
449
|
+
The key insight is that the 6510's program counter is just another register — once Part N has finished its effect and launched Part N+1, Part N's code is irrelevant. Reusing the same memory range for each successive part is safe as long as the code that initiates the load (the transition code) lives entirely in the persistent zone and the load destination is not the persistent zone itself.
|
|
450
|
+
|
|
451
|
+
The persistent music zone (`$0800-$0FFF` in the layout above) requires careful negotiation: if Part N+1 brings its own music, it must either load to a different address than the current music, or signal Part N's IRQ to stop calling the old SID player before overwriting it. The standard solution is a "music handshake" flag: Part N+1's init code sets a flag, Part N's IRQ reads it and stops calling the player, and Part N+1's init code then overwrites the music area and installs the new player pointer.
|
|
452
|
+
|
|
453
|
+
### Variations
|
|
454
|
+
|
|
455
|
+
**Load during effects vs load-then-start.** The simpler approach is to load the entire next part before starting its effect — load screen displayed, then JMP. The sophisticated approach (concurrent load + effect) requires that the effect code use only persistent-zone resources. Which approach is appropriate depends on whether the production has a "loading screen" aesthetic or demands seamless flow.
|
|
456
|
+
|
|
457
|
+
**Packed vs unpacked part files.** Loading a packed file via Krill + Exomizer into the part area and decompressing in-place is the most space-efficient strategy. The part area must be large enough to hold the packed data plus the headroom for the depacker to work (the default depacker expands in-place from high address to low, so no extra buffer is needed for the Exomizer `raw` format — see `exomizer_basics`).
|
|
458
|
+
|
|
459
|
+
**Part numbering and disk layout.** For fastest sequential loading, lay the parts out on disk in the order they will be read. The 1541's track-seek time is the dominant latency for multi-part sequencing: seeking from track 5 to track 30 takes approximately 500ms; sequential parts on adjacent tracks load with minimal seek overhead. Krill does not manage disk layout; use a custom disk-image builder (e.g., `cc1541` or `cbmconvert`) to place files in the desired track order.
|
|
460
|
+
|
|
461
|
+
### Cycle budget
|
|
462
|
+
|
|
463
|
+
Per-part load time (Krill fast loader, 50 KB packed part, ~30 KB after Exomizer compression): approximately 4 s transfer plus 2-5 s decompression (Exomizer measured at roughly 4-6 s per 50 KB of output, see `exomizer_basics`; faster depackers toward the low end) — roughly 6-9 s total. A 5-second transition does NOT cover this on its own: either decompress while the next transfer streams (Krill's `loadcompd` path, ideally with a faster decompressor such as ZX0 or TSCrunch) or budget the transition for 6-9 s. An earlier version of this page costed decompression at 50 ms and claimed 0.95 s of margin. Timing varies with disk geometry and seek distance — measure on real hardware or in VICE with accurate 1541 emulation enabled.
|
|
464
|
+
|
|
465
|
+
### Recipes
|
|
466
|
+
|
|
467
|
+
- No recipe yet. (An earlier version of this page pointed at `recipes/kickassembler/cracktro-template.md`; it is a one-part PRG whose only hand-off is a fire-button JMP to a configured entry address, with no load between parts.)
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## crunched_data_in_basic_stub — Compressing the BASIC stub
|
|
472
|
+
|
|
473
|
+
**Complexity:** low
|
|
474
|
+
**Region:** both
|
|
475
|
+
**Uses kernal:** (none)
|
|
476
|
+
|
|
477
|
+
### Why
|
|
478
|
+
|
|
479
|
+
Every C64 PRG file that auto-runs loads to `$0801` and begins with the standard BASIC stub: two bytes of link address, two bytes of line number, a `SYS` token, the address digits as PETSCII characters, and a pair of null bytes for end-of-line and end-of-program. The canonical form `10 SYS2061` (link, line number, `$9E`, the four PETSCII digits, EOL, end-of-program: `0B 08 0A 00 9E 32 30 36 31 00 00 00`) occupies 12 bytes; the first machine-code instruction sits at `$080D` (decimal 2061), and `$080D - $0801 = 12`. An earlier version of this page said 13 bytes, which is probably KickAssembler's default `BasicUpstart2`: it emits `10 SYS2062` with one pad byte (13 bytes to `$080D`, code at `$080E`). For a 256-byte intro, 12 bytes of stub overhead before the first instruction byte is about 4.7% of the total budget. For a 1-kilobyte intro it is 1.2%. For any production where byte count matters, eliminating or compressing this overhead is worthwhile.
|
|
480
|
+
|
|
481
|
+
The technique of placing the compressed payload immediately after the SYS and pointing the SYS target to a depacker changes the layout so that the entire PRG (from `$0801` onward) is either BASIC stub or executable code — there is no gap of dead bytes and no separate load address overhead beyond the 2-byte PRG load address prefix.
|
|
482
|
+
|
|
483
|
+
### How
|
|
484
|
+
|
|
485
|
+
The compressed-stub layout:
|
|
486
|
+
|
|
487
|
+
```
|
|
488
|
+
$0000-$0001 PRG load address (2 bytes: $01, $08 — loads to $0801)
|
|
489
|
+
$0801-$080C BASIC stub: 10 SYS 2061 (12 bytes)
|
|
490
|
+
$080D-$08D5 Depacker/entry code (e.g., the b2 -c sfx form: 201 bytes, which copy
|
|
491
|
+
a 183-byte decruncher to zero page $0010 before running it)
|
|
492
|
+
$08D6-$... Packed payload (compressed code + data)
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
The SYS target (`2061` = `$080D`) jumps directly to the depacker. The depacker reads the packed payload beginning at the byte immediately following its last instruction, decompresses to the original target address (which could be `$0801` itself, or any other address), then JMPs to the original entry point.
|
|
496
|
+
|
|
497
|
+
For a 256-byte intro the arithmetic does not favour a general-purpose packer:
|
|
498
|
+
|
|
499
|
+
- BASIC stub: 12 bytes
|
|
500
|
+
- ByteBoozer 2 sfx stub (BASIC line + entry + decruncher): 213 bytes in total, measured from `b2 -c 0801` in Krill v194
|
|
501
|
+
- Remaining for packed payload: 256 - 2 (PRG header) - 213 = **41 bytes**
|
|
502
|
+
|
|
503
|
+
An earlier version of this page put the depacker at 85 bytes and computed 156 bytes of payload expanding to ~223 bytes of effect code; with the measured stub size that budget does not exist, which is why 256-byte intros hand-roll their own tiny depackers (or none) rather than embed ByteBoozer or Exomizer.
|
|
504
|
+
|
|
505
|
+
### Why it works
|
|
506
|
+
|
|
507
|
+
The BASIC interpreter runs the SYS statement, which calls `SYS` at `$080D`. At that point the BASIC program is "running" but control has immediately exited to machine code, and the BASIC interpreter's state is no longer used. The depacker occupies the same address space as the beginning of the machine code section and overwrites its own bytes after they have been executed — legal because the CPU fetches instructions sequentially and never revisits the already-executed depacker bytes after they are done. The packed payload bytes are non-executable until after decompression; they sit in the memory layout as inert data until the depacker reads and expands them.
|
|
508
|
+
|
|
509
|
+
An important constraint: the depacker itself must not overlap its output destination until the corresponding output byte has been written. ByteBoozer and Exomizer both guarantee this by construction (they decompress in a direction that always reads packed source bytes before they are overwritten by expanded output). Doynax LZ has the same guarantee. A custom depacker that naively decompresses forward without checking this invariant can corrupt itself.
|
|
510
|
+
|
|
511
|
+
### Variations
|
|
512
|
+
|
|
513
|
+
**Zero-page depacker.** Instead of placing the depacker at `$080D`, a very small depacker (under 30 bytes) can reside in free zero-page space (`$02-$7F` is largely free on a stock C64 with KERNAL + BASIC disabled, though care is needed around VIC-II pointer ZP variables and KERNAL workspace). This frees the `$0801-$...` area entirely for the payload. Assembling to zero page requires position-independent or zero-page-addressed code.
|
|
514
|
+
|
|
515
|
+
**Combined load + unpack stub.** A self-installing production that loads additional packed files can place the Krill resident at its default `$0200-$0407` (~520 B in the v194 prebuilt; a raw-load-only build is ~250 B), which the BASIC stub does not reach, leaving the `$0800` range available for the effect proper. The resident must be copied there only *after* `install` returns (install's KERNAL calls write the file tables at `$0259+`), and the installer itself is ~3-7 KB of transient code, so this is a two-stage boot whose first stage is a few kilobytes, not 256 bytes — an earlier version of this page put the stubs in 256 bytes at `$0200-$02FF`. See `multi_load_sequencing` for the layout.
|
|
516
|
+
|
|
517
|
+
**BASIC stub compression.** Technically the BASIC stub itself (`10 SYS 2061`) can be shortened: `0 SYS826` uses a line number of 0 and decimal address 826 = `$033A`. If the depacker can live at `$033A` (free on a stock machine with KERNAL ROM disabled), the BASIC stub shrinks from 12 to 11 bytes. Combined with the zero-page depacker variant, this squeezes 1 more byte into the payload at the cost of careful ZP + low-RAM placement (an earlier version of this page counted the saving as 2 bytes, from the 13-byte figure corrected above).
|
|
518
|
+
|
|
519
|
+
### Cycle budget
|
|
520
|
+
|
|
521
|
+
The depacker execution overhead (BASIC calls SYS, depacker runs, jumps to effect entry) is small for a tiny payload: a 200-byte payload decompresses in roughly 10,000-20,000 cycles (10-20 ms) at tens of cycles per output byte, so the SYS-plus-depack overhead is invisible to the user — the PRG loads in under a second and the effect begins immediately. For larger payloads, scale proportionally: 2 KB of packed output at tens of cycles per byte is on the order of 0.1-0.2 s, still negligible. (An earlier version of this page gave "100-300 ms for a 200-byte payload" and "85 bytes/ms ... roughly 24 ms for 2 KB", two figures that disagreed with each other by 40-100x; the 85 bytes/ms rate is 11.6 cycles per byte, below the 16-cycle cost of a bare `LDA (zp),Y` / `STA (zp),Y` / `INY` / `BNE` copy loop.)
|
|
522
|
+
|
|
523
|
+
### Recipes
|
|
524
|
+
|
|
525
|
+
- No recipe yet. (An earlier version of this page pointed at `recipes/kickassembler/cracktro-template.md`; that recipe is assembled to a plain PRG and is not packed with Exomizer, ByteBoozer or any cruncher.)
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## runtime_relocation — Load a code overlay at a run-time address and relocate it
|
|
530
|
+
|
|
531
|
+
**Complexity:** medium
|
|
532
|
+
**Region:** both
|
|
533
|
+
**Cost:** bytes_code=43, zp_bytes=4
|
|
534
|
+
**Cost basis:** derived-listing
|
|
535
|
+
**Cost measured on:** kickassembler-runtime-relocation (one-off per load)
|
|
536
|
+
|
|
537
|
+
### Why
|
|
538
|
+
|
|
539
|
+
A game that outgrows 64 KB keeps per-level code on disk: a level script,
|
|
540
|
+
a boss routine, a cutscene. Build-time placement (`.pseudopc`) fixes the
|
|
541
|
+
address when you assemble. When the free block is only known at run
|
|
542
|
+
time, because levels load different graphics and music and a heap hands
|
|
543
|
+
out what is left, the code has to be moved after it is loaded. Metal
|
|
544
|
+
Warrior 4 loads its event scripts as 2 KB chunks of native code with a
|
|
545
|
+
jump table at the start, named by a 16-bit id (high byte the file, low
|
|
546
|
+
byte the entry point), instead of running an interpreter (Cadaver's
|
|
547
|
+
MW4 article; not measured here).
|
|
548
|
+
|
|
549
|
+
### How
|
|
550
|
+
|
|
551
|
+
1. Assemble the overlay twice, at two origins one page apart (the recipe
|
|
552
|
+
uses `$3000` and `$3100`).
|
|
553
|
+
2. Diff the two images. A byte one higher in the second image is treated as the
|
|
554
|
+
high byte of an address inside the overlay; record its offset. A byte
|
|
555
|
+
that is equal is an opcode, a low byte, a zero-page operand, a
|
|
556
|
+
constant or an address outside the overlay, and is left alone. A byte
|
|
557
|
+
that moved by any other amount cannot be page-relocated: refuse the
|
|
558
|
+
build.
|
|
559
|
+
3. Ship the first image plus the table of offsets (two bytes each for an
|
|
560
|
+
overlay over 256 bytes).
|
|
561
|
+
4. At run time, take a block of whole pages from the heap, load the
|
|
562
|
+
image there, and add `delta` = load page − origin page to each listed
|
|
563
|
+
byte.
|
|
564
|
+
5. Enter the overlay only through a jump table at a fixed offset
|
|
565
|
+
(`JMP` at offsets 0, 3, 6, …). The host knows the overlay's page and
|
|
566
|
+
those offsets, nothing else.
|
|
567
|
+
|
|
568
|
+
The heap can be a bump pointer by pages (free everything at level end),
|
|
569
|
+
or a first-fit list of page runs if overlays come and go during play.
|
|
570
|
+
c64gameframework uses a bump pointer that compacts: loading a chunk ages
|
|
571
|
+
the others from `C_FIRSTPURGEABLE` up, purging one moves every chunk
|
|
572
|
+
above it down, relocates those chunks again by the new delta, and
|
|
573
|
+
patches return addresses on the stack that point into them (`file.s`,
|
|
574
|
+
`PurgeFile`, read here, not run). Because relocation is an add, a copy
|
|
575
|
+
that has moved can be relocated again with the same table, as long as
|
|
576
|
+
nothing has rewritten a listed byte.
|
|
577
|
+
|
|
578
|
+
### Why it works
|
|
579
|
+
|
|
580
|
+
Moving by whole pages changes only high bytes, and each by the same
|
|
581
|
+
`delta`. Low bytes, and every byte's position within its page, stay the
|
|
582
|
+
same. So page-aligned tables stay aligned, and no branch or indexed read
|
|
583
|
+
gains or loses a page-crossing cycle: the relocated copy runs in the
|
|
584
|
+
same cycles as the original. The diff finds the immediate `#>label` and
|
|
585
|
+
the high bytes of address words in data tables, which a scan of the
|
|
586
|
+
instruction stream does not.
|
|
587
|
+
|
|
588
|
+
### What cannot be relocated this way
|
|
589
|
+
|
|
590
|
+
- An address stored as its low byte only (`#<label` alone, a split
|
|
591
|
+
low-byte table without its high half) is fine, because whole-page moves
|
|
592
|
+
leave low bytes alone. A byte-granular move would break it; do not
|
|
593
|
+
offer one.
|
|
594
|
+
- An expression that is not a plain high byte: `label / 64` (a sprite
|
|
595
|
+
pointer), `label >> 4`. It moves by some other amount; the diff
|
|
596
|
+
refuses it. `>label + 1` moves by exactly one and is relocated
|
|
597
|
+
correctly (assembled at $3000 and $3100 with KickAssembler 5.25).
|
|
598
|
+
- A byte that moves by one but is not an address is relocated wrongly
|
|
599
|
+
and silently. An offset measured from a fixed origin constant,
|
|
600
|
+
`>(label - $3000)`, is $00 in one image and $01 in the other, so it is
|
|
601
|
+
listed and gets the delta added. Measure offsets from an overlay label
|
|
602
|
+
(`label - base`, where `base` is the overlay's first label); that
|
|
603
|
+
expression is the same in both images.
|
|
604
|
+
- An operand the overlay builds at run time from its own address (self-
|
|
605
|
+
modifying code that computes an address with arithmetic rather than
|
|
606
|
+
`#>label`) is invisible to the diff. Build such operands from
|
|
607
|
+
`#<label` and `#>label` immediates, which are relocated.
|
|
608
|
+
- An address the host stores that points into the overlay (an IRQ
|
|
609
|
+
vector, a callback) must come from the jump table after relocation,
|
|
610
|
+
not from a symbol file.
|
|
611
|
+
- An overlay that has rewritten one of its own listed bytes is no
|
|
612
|
+
longer the image the table describes. Relocate a fresh load, or keep
|
|
613
|
+
listed bytes out of self-modification if the heap compacts.
|
|
614
|
+
|
|
615
|
+
### Variations
|
|
616
|
+
|
|
617
|
+
**Instruction walker.** Cadaver's scheme assembles overlays at `$8000`,
|
|
618
|
+
inside the dynamic area, and needs no table: a walker steps through the
|
|
619
|
+
code using a 32-byte packed table of instruction lengths, adds the
|
|
620
|
+
delta to every 3-byte operand whose high byte falls inside the dynamic
|
|
621
|
+
area, and stops at a `BRK`. It forbids `BIT`-skip tricks, since they hide
|
|
622
|
+
an instruction inside another's operand, and it cannot see address
|
|
623
|
+
tables, which it replaces with run-time resource lookups (Cadaver's
|
|
624
|
+
relocation article; not measured here). It saves the table's disk space and
|
|
625
|
+
costs a walk over every instruction.
|
|
626
|
+
|
|
627
|
+
**Bitmap instead of offsets.** One bit per overlay byte costs size / 8
|
|
628
|
+
bytes whatever the count; an offset list costs 2 bytes per entry. The
|
|
629
|
+
list is smaller when fewer than one byte in 16 is relocated (arithmetic).
|
|
630
|
+
Dense code (one in 4.4 in the recipe) favours the bitmap; an overlay that
|
|
631
|
+
is mostly data favours the list.
|
|
632
|
+
|
|
633
|
+
### Cycle budget
|
|
634
|
+
|
|
635
|
+
Relocation runs once per load, outside the frame loop, so the Cost line
|
|
636
|
+
carries no per-frame figure. In the recipe, one relocation of a 340-byte
|
|
637
|
+
overlay with 19 listed bytes took 855 cycles: 20 fixed plus 44 per byte while the
|
|
638
|
+
offset tables do not cross a page (one more cycle per read past 213
|
|
639
|
+
entries),
|
|
640
|
+
45.0 per byte overall (measured in VICE x64sc, CIA2 timer, screen
|
|
641
|
+
blanked, PAL and NTSC). The overlay's 79 bytes of code carry 18 of the
|
|
642
|
+
19 entries, one byte in 4.4; a 2 KB chunk of such code would have about
|
|
643
|
+
465 entries and take about 20,500 cycles, about one PAL frame of 19,656
|
|
644
|
+
(arithmetic from the measured per-byte cost). The recipe's count is one
|
|
645
|
+
byte, so a table over 255 entries needs a 16-bit count. The
|
|
646
|
+
43 bytes of code are the recipe's `relocate` routine; the 4 zero-page
|
|
647
|
+
bytes are its pointer, the destination page and `delta`.
|
|
648
|
+
|
|
649
|
+
### Recipes
|
|
650
|
+
|
|
651
|
+
- `recipes/kickassembler/runtime-relocation.md`: assembles one overlay
|
|
652
|
+
twice with `.pseudopc`, diffs the images into a table on the C64 (and
|
|
653
|
+
gives the host-side Python for shipping), relocates two copies from a
|
|
654
|
+
page heap and checks both through their jump tables.
|
|
655
|
+
|
|
656
|
+
### Sources
|
|
657
|
+
|
|
658
|
+
- Cadaver, relocation article: https://cadaver.github.io/rants/relocation.html
|
|
659
|
+
- Cadaver, Metal Warrior 4 script chunks: https://cadaver.github.io/rants/mw4trick.html
|
|
660
|
+
- c64gameframework (MIT), `main.s` and `file.s`: https://github.com/cadaver/c64gameframework
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## iffl_single_file — All level files in one disk file, found by a scanned offset table
|
|
665
|
+
|
|
666
|
+
**Complexity:** medium
|
|
667
|
+
**Region:** both
|
|
668
|
+
**Uses kernal:** SETNAM, SETLFS, OPEN, CHKIN, CHRIN, CLRCHN, CLOSE, READST
|
|
669
|
+
|
|
670
|
+
### Why
|
|
671
|
+
|
|
672
|
+
A multi-load game keeps each level's graphics, map and music on disk.
|
|
673
|
+
One disk file per level costs a directory entry each, and the 1541
|
|
674
|
+
directory holds 144 (`formats/c64-file-formats.md`). Every load also
|
|
675
|
+
starts with a directory search by name. IFFL puts all the data files
|
|
676
|
+
into one disk file: one directory entry, no name search after the first,
|
|
677
|
+
and a loader that goes straight to file N. Cadaver notes it in some newer
|
|
678
|
+
cracks of multi-load games.
|
|
679
|
+
|
|
680
|
+
### How
|
|
681
|
+
|
|
682
|
+
1. **Pack** on the PC. Concatenate the files with nothing between them
|
|
683
|
+
and put a table of their lengths in front. Cadaver's format fills the
|
|
684
|
+
first block with it: 127 low bytes, then 127 high bytes, 254 bytes in
|
|
685
|
+
all, so the data starts at the second block. That allows 127 files
|
|
686
|
+
of up to 65,535 bytes each.
|
|
687
|
+
2. **Scan** once, at game start. Walk the file and record, for each
|
|
688
|
+
subfile, where it starts: the track and sector of the block, and the
|
|
689
|
+
byte offset in that block. With a 16-bit counter per file, the scan
|
|
690
|
+
only has to follow the chain of track/sector links, not transfer the
|
|
691
|
+
data (Cadaver). The table costs 3 bytes per subfile (track, sector,
|
|
692
|
+
offset), 381 bytes for a full 127, plus the lengths (arithmetic).
|
|
693
|
+
3. **Load** subfile N. Read its first block, send the C64 only the bytes
|
|
694
|
+
from the recorded offset on, then follow the links until the length
|
|
695
|
+
runs out.
|
|
696
|
+
|
|
697
|
+
The scan and the mid-file start are custom drive code. The 1541 DOS
|
|
698
|
+
opens a file only at its start, and the C64 KERNAL has no seek on a
|
|
699
|
+
sequential channel. So:
|
|
700
|
+
|
|
701
|
+
| Part | Needs drive code | KERNAL-only form |
|
|
702
|
+
|---|---|---|
|
|
703
|
+
| Pack the file | no (PC side) | the same file |
|
|
704
|
+
| Scan | yes, to record track/sector/offset | read the length table and add it up to byte offsets |
|
|
705
|
+
| Start mid-file | yes | open the file and read and discard every byte before subfile N |
|
|
706
|
+
| Transfer speed | a fast-loader protocol | serial bus at KERNAL speed |
|
|
707
|
+
|
|
708
|
+
The KERNAL-only form keeps the one directory entry and works on any
|
|
709
|
+
drive, including ones that run no custom code. It loses the seek: every
|
|
710
|
+
skipped byte crosses the bus like a kept one.
|
|
711
|
+
|
|
712
|
+
### Why it works
|
|
713
|
+
|
|
714
|
+
A disk file is a chain of 256-byte blocks, each starting with the track
|
|
715
|
+
and sector of the next and carrying 254 data bytes. Once the scan has
|
|
716
|
+
turned "subfile N" into "this block, this byte", a drive-side loader
|
|
717
|
+
can read that block directly, as the DOS does for any block, without
|
|
718
|
+
the directory or the earlier blocks. A byte offset in the file maps to
|
|
719
|
+
block `offset / 254`, data byte `offset % 254` of the chain
|
|
720
|
+
(arithmetic); the recipe prints these pairs.
|
|
721
|
+
|
|
722
|
+
### Variations
|
|
723
|
+
|
|
724
|
+
**KERNAL skip loader.** The fallback when the fast loader cannot
|
|
725
|
+
install (an SD2IEC, a drive that is not 1541-compatible). Measured in
|
|
726
|
+
`recipes/oscar64/iffl-kernal-skip.md`: the time to load subfile N rises
|
|
727
|
+
by about 2,526 cycles on PAL (2,621 on NTSC) for every byte before its
|
|
728
|
+
end, plus about 0.44 million cycles of `OPEN` and `CLOSE` per load. At
|
|
729
|
+
that rate the last subfile of a file filling a whole disk, 168,656
|
|
730
|
+
bytes, is about 7 minutes away (arithmetic). Keep the KERNAL form to
|
|
731
|
+
small files, or put the most-loaded subfiles first.
|
|
732
|
+
|
|
733
|
+
**Lengths up front.** If the loader can start at a byte offset, the
|
|
734
|
+
lengths give each subfile's byte offset by arithmetic. A drive-side
|
|
735
|
+
loader still has to walk the chain, because the DOS writes a file's
|
|
736
|
+
blocks with an interleave (`formats/c64-file-formats.md`), so the
|
|
737
|
+
position of block K cannot be worked out from the first block.
|
|
738
|
+
|
|
739
|
+
**IFFL with a fast loader.** `krill_loader_integration` and
|
|
740
|
+
`sparkle_irq_loader` load by name or by an index into their own file
|
|
741
|
+
table; neither page here documents an IFFL mode, so check the loader's
|
|
742
|
+
own docs before assuming one. Level sequencing on top of either is
|
|
743
|
+
`multi_load_sequencing`.
|
|
744
|
+
|
|
745
|
+
### Cycle budget
|
|
746
|
+
|
|
747
|
+
The scan and the loads run between levels, outside the frame loop, so
|
|
748
|
+
there is no Cost line. In the KERNAL recipe (VICE x64sc, CIA2 timer,
|
|
749
|
+
five subfiles, 1,615 bytes): the scan, one `OPEN` and 11 bytes, took
|
|
750
|
+
475,342 cycles on PAL with the drive already running and 1,824,005 on
|
|
751
|
+
first access to a packer-built disk (spin-up included); the loads took
|
|
752
|
+
974,300 cycles (subfile 1, 211 bytes read) to 4,521,555 cycles (subfile
|
|
753
|
+
5, 1,615 bytes read) over all runs. The
|
|
754
|
+
cost of the drive-code scan and seek was not measured here.
|
|
755
|
+
|
|
756
|
+
### Recipes
|
|
757
|
+
|
|
758
|
+
- `recipes/oscar64/iffl-kernal-skip.md` — packs five subfiles behind a
|
|
759
|
+
length table (Python packer, and the same bytes written on the C64
|
|
760
|
+
when the disk is empty), scans the table, loads subfiles 3, 1, 2, 4
|
|
761
|
+
and 5 by skipping with KERNAL `CHRIN`, and checks and times each.
|
|
762
|
+
No recipe yet for the drive-code scan and seek.
|
|
763
|
+
|
|
764
|
+
### Sources
|
|
765
|
+
|
|
766
|
+
- Cadaver, "IFFL system": https://cadaver.github.io/rants/iffl.html
|
|
767
|
+
|
|
768
|
+
---
|