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,1191 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: loader
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- doc-type: pitfall-reference -->
|
|
6
|
+
|
|
7
|
+
# Loader Pitfalls
|
|
8
|
+
|
|
9
|
+
The pitfalls in this document share a common thread: they all arise from
|
|
10
|
+
the gap between what a fast loader expects and what the surrounding program
|
|
11
|
+
(or its build toolchain) provides. Fast loaders are invasive by design — they patch KERNAL vectors, bypass
|
|
12
|
+
the 1541 ROM entirely, and assume that the drive's hardware is exactly as stock as
|
|
13
|
+
the day it left the factory. Any code that assumes it can restore "normal" state,
|
|
14
|
+
any hardware that deviates from 1541 timing, and any program data layout that
|
|
15
|
+
conflicts with the BASIC stub can each silently corrupt loading in ways that look
|
|
16
|
+
like intermittent hardware failure. The pitfalls below have burned every C64
|
|
17
|
+
coder who encountered them for the first time: the symptoms appear random but the
|
|
18
|
+
mechanisms are completely deterministic.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## fastloader_kernal_dependency — Fastloader KERNAL-vector patch breaks after IRQ setup
|
|
23
|
+
|
|
24
|
+
**Severity:** high
|
|
25
|
+
**Region:** both
|
|
26
|
+
**Triggered by kernal:** LOAD
|
|
27
|
+
**Triggered by techniques:** multi_load_sequencing
|
|
28
|
+
|
|
29
|
+
### Symptom
|
|
30
|
+
|
|
31
|
+
A production installs a `$FFD5`-hooking fast loader successfully during startup. The first
|
|
32
|
+
part loads at full speed. After the first part installs its own raster IRQ handler
|
|
33
|
+
— or after any block of initialization code that includes "restore KERNAL vectors"
|
|
34
|
+
as a housekeeping step — subsequent LOAD calls revert to the slow KERNAL serial
|
|
35
|
+
protocol. The second part loads in 130 seconds instead of 6 seconds. On hardware
|
|
36
|
+
with a serial bus that has even marginal IEC signal quality, the slow KERNAL
|
|
37
|
+
protocol may also produce read errors that never appeared with the fast path
|
|
38
|
+
active.
|
|
39
|
+
|
|
40
|
+
A subtler variant: code that calls KERNAL `RESTOR` (`$FF8A`, no arguments — an
|
|
41
|
+
earlier version of this page said `$FF8D, mode=0`; `$FF8D` is `VECTOR`) at the
|
|
42
|
+
start of initialization resets all sixteen KERNAL RAM vectors `$0314-$0333` to
|
|
43
|
+
their ROM defaults, including `$0330/$0331` (the LOAD vector). This silently undoes
|
|
44
|
+
the fastloader installation before the first LOAD call ever happens.
|
|
45
|
+
|
|
46
|
+
### Mechanism
|
|
47
|
+
|
|
48
|
+
Loaders that interpose on the KERNAL LOAD hook `ILOAD` at `$0330/$0331` so that
|
|
49
|
+
`JSR $FFD5` takes the fast path; the classic cartridge fastloaders work this way.
|
|
50
|
+
The KERNAL jump table entry at `$FFD5` (LOAD) is `JMP $F49E`; `$F49E` saves X/Y
|
|
51
|
+
to `$C3/$C4` and then jumps through `$0330` (`ILOAD`, two bytes, little-endian),
|
|
52
|
+
whose ROM default is `$F4A5` (KERNAL 901227-03, read from the `$FD30` vector
|
|
53
|
+
table — rung 1; an earlier version of this page gave `$FA31`, which is inside the
|
|
54
|
+
tape-read code). A hooking loader overwrites `$0330/$0331` with the address of
|
|
55
|
+
its own C64-side receive loop, and all subsequent `JSR $FFD5` calls take the fast
|
|
56
|
+
path transparently.
|
|
57
|
+
|
|
58
|
+
Krill v194 does NOT work this way, and neither does Sparkle — an earlier version
|
|
59
|
+
of this page said both did. Krill's API (`install`/`loadraw`/`loadcompd`) is
|
|
60
|
+
called directly and its source contains no write to `$0330` — not even in
|
|
61
|
+
`LOAD_VIA_KERNAL_FALLBACK` mode, whose fallback path calls `OPEN`/`CHKIN`/`BASIN`
|
|
62
|
+
byte-by-byte rather than `$FFD5` (from the v194 source as read for this
|
|
63
|
+
correction; the source is not on this machine, so rung 4 here — the technique
|
|
64
|
+
page `../techniques/loaders-packers.md` "v194 concrete integration reference"
|
|
65
|
+
agrees that the documented usage never goes through `$FFD5`). Sparkle's IRQ
|
|
66
|
+
loader uses no KERNAL routine at all (see `sparkle_irq_loader`, "Uses kernal:
|
|
67
|
+
(none)"). For Krill and Sparkle this pitfall does not apply: restoring `$0330` or
|
|
68
|
+
calling `RESTOR` (`$FF8A`) leaves them working. The real Krill hazards are listed
|
|
69
|
+
at the end of the Fix below.
|
|
70
|
+
|
|
71
|
+
The problem arises when anything restores these vectors to their default ROM
|
|
72
|
+
targets. Three common culprits:
|
|
73
|
+
|
|
74
|
+
1. **Generic "restore KERNAL" init sequences.** Many tutorials and old code
|
|
75
|
+
bases include a block like this as part of IRQ installation:
|
|
76
|
+
|
|
77
|
+
```kick
|
|
78
|
+
sei
|
|
79
|
+
lda #$a5 // ROM ILOAD lo
|
|
80
|
+
sta $0330
|
|
81
|
+
lda #$f4 // ROM ILOAD hi ($F4A5 = LOAD body in KERNAL 901227-03; the $FD30 table value. $FFD5 -> $F49E does JMP ($0330))
|
|
82
|
+
sta $0331
|
|
83
|
+
cli
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
(An earlier version of this block wrote `$FA31`, which is inside the KERNAL's
|
|
87
|
+
tape-read code, and spelled the mnemonics in capitals, which KickAssembler 5.25
|
|
88
|
+
rejects — "Pseudo command SEI not defined"; the fence had never been assembled.)
|
|
89
|
+
|
|
90
|
+
This pattern appears in generic IRQ-setup templates where the author
|
|
91
|
+
intended only to stabilize the IRQ vector (`$0314/$0315`) but copied a
|
|
92
|
+
broader vector-reset block that also clobbers the LOAD vector.
|
|
93
|
+
|
|
94
|
+
2. **KERNAL RESTOR call.** `JSR $FF8A` restores all sixteen KERNAL RAM vectors
|
|
95
|
+
`$0314-$0333` to their ROM defaults in a single call; it takes no argument
|
|
96
|
+
(`VECTOR` at `$FF8D` with carry clear writes the same block from a
|
|
97
|
+
caller-supplied table at X/Y, and with carry set reads it out). An earlier
|
|
98
|
+
version of this page said `JSR $FF8D` with A=0 and fifteen vectors; the ROM
|
|
99
|
+
bytes are `$FF8A: JMP $FD15` (RESTOR: `LDX #$30 / LDY #$FD / CLC`, falling
|
|
100
|
+
into VECTOR) and `$FF8D: JMP $FD1A` (VECTOR, which copies `$1F`+1 = 32 bytes
|
|
101
|
+
and selects direction on the carry), rung 1. Any init sequence that includes
|
|
102
|
+
`JSR $FF8A` after a hooking loader's install routine runs will silently undo
|
|
103
|
+
the installation.
|
|
104
|
+
|
|
105
|
+
3. **Cold-start or warm-start flow.** A production that jumps to `$FCE2`
|
|
106
|
+
(KERNAL RESET / cold start) or `$FE66` (the KERNAL RUN/STOP-RESTORE warm
|
|
107
|
+
start) as part of a "reset to safe state" sequence will reinitialize RAM
|
|
108
|
+
vectors — both paths run RESTOR (`$FD15`, the body behind `$FF8A`), which
|
|
109
|
+
rewrites `$0314-$0333` from the ROM table. An earlier version of this page
|
|
110
|
+
named `$FD15` itself as the warm start; it is the RESTOR body, rung 1 from
|
|
111
|
+
the ROM bytes. This is rare but occasionally appears in cracktros that chain
|
|
112
|
+
off a previous production's reset path.
|
|
113
|
+
|
|
114
|
+
The fastloader's drive-side code is unaffected — it remains running in the
|
|
115
|
+
1541's RAM until the drive is reset. The C64 side is what breaks: the receive
|
|
116
|
+
loop address is gone, and `JSR $FFD5` now calls the slow ROM routine. Because
|
|
117
|
+
the drive is still in its fast-protocol mode, the slow ROM routine and the fast
|
|
118
|
+
drive protocol are completely mismatched. Depending on the loader and the
|
|
119
|
+
drive state, this manifests as extremely slow loading (the drive times out and
|
|
120
|
+
falls back to a safe state), as a hung bus (the drive is waiting for the fast
|
|
121
|
+
handshake that never comes), or as a "?FILE NOT FOUND" error.
|
|
122
|
+
|
|
123
|
+
### Fix
|
|
124
|
+
|
|
125
|
+
Two approaches, depending on what the IRQ setup code actually needs:
|
|
126
|
+
|
|
127
|
+
**Option A — Save and restore the LOAD vector around IRQ install:**
|
|
128
|
+
|
|
129
|
+
```kick
|
|
130
|
+
sei
|
|
131
|
+
|
|
132
|
+
// Save the current LOAD vector (fast path or ROM, whichever is live)
|
|
133
|
+
lda $0330
|
|
134
|
+
sta saved_load_lo
|
|
135
|
+
lda $0331
|
|
136
|
+
sta saved_load_hi
|
|
137
|
+
|
|
138
|
+
// Install IRQ handler into $0314/$0315 only — do NOT touch $0330/$0331
|
|
139
|
+
lda #<irq_handler
|
|
140
|
+
sta $0314
|
|
141
|
+
lda #>irq_handler
|
|
142
|
+
sta $0315
|
|
143
|
+
|
|
144
|
+
// Restore the LOAD vector in case anything above clobbered it
|
|
145
|
+
lda saved_load_lo
|
|
146
|
+
sta $0330
|
|
147
|
+
lda saved_load_hi
|
|
148
|
+
sta $0331
|
|
149
|
+
|
|
150
|
+
cli
|
|
151
|
+
|
|
152
|
+
// ...
|
|
153
|
+
|
|
154
|
+
saved_load_lo: .byte 0
|
|
155
|
+
saved_load_hi: .byte 0
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
(An earlier version of this listing was in capitals, which KickAssembler 5.25
|
|
159
|
+
rejects; it now assembles.) Both the save and the restore sit inside the
|
|
160
|
+
`SEI`/`CLI` window here only to keep the example compact; in practice, save the
|
|
161
|
+
vector before any code that might disturb it, and restore it as soon as that code
|
|
162
|
+
has run — before `CLI` if an IRQ handler reads the LOAD vector. (An earlier
|
|
163
|
+
version of this sentence said the save was "before `SEI`", which described a
|
|
164
|
+
different listing.)
|
|
165
|
+
|
|
166
|
+
**Option B — Audit the init sequence and remove LOAD-vector writes:**
|
|
167
|
+
|
|
168
|
+
Search the codebase for writes to `$0330` and `$0331` and for calls to
|
|
169
|
+
`JSR $FF8A` (RESTOR) and `JSR $FF8D` (VECTOR, which with carry clear writes the
|
|
170
|
+
same block from a caller-supplied table; an earlier version of this page named
|
|
171
|
+
only `$FF8D`, so a search that followed it missed every real `JSR $FF8A`). Unless
|
|
172
|
+
there is a deliberate reason to restore the LOAD vector,
|
|
173
|
+
delete those writes. The IRQ vector at `$0314/$0315` can be patched without
|
|
174
|
+
touching the LOAD vector — they are independent RAM vectors.
|
|
175
|
+
|
|
176
|
+
**What this page used to say, and no longer does.** An earlier version carried an
|
|
177
|
+
"Option C — re-run Krill install after IRQ setup", claiming the installer was
|
|
178
|
+
idempotent and re-patched `$0330/$0331`, and a paragraph saying Sparkle's raster
|
|
179
|
+
IRQ dispatched through the LOAD vector on every block. Neither loader touches
|
|
180
|
+
`$0330`, so there is no vector to re-patch; Krill's `install` tests CIA2 DDRA for
|
|
181
|
+
an existing installation and returns OK without doing anything, and if that test
|
|
182
|
+
fails while the drive is already in loader mode a second `install` hangs on the
|
|
183
|
+
KERNAL serial path (rung 4 here — the Krill source is not on this machine). Both
|
|
184
|
+
passages were deleted.
|
|
185
|
+
|
|
186
|
+
**The real Krill hazards** are the ones `../techniques/loaders-packers.md`
|
|
187
|
+
already documents, and they have nothing to do with `$0330`:
|
|
188
|
+
|
|
189
|
+
- Any raw write to `$DD00`/`$DD02` (a VIC-bank switch, a generic CIA init) while
|
|
190
|
+
Krill is armed corrupts its bus-lock/installed-state test — see
|
|
191
|
+
`fastloader_dd00_write_corrupts_resident` below. This is Krill's rule only;
|
|
192
|
+
Sparkle prescribes a `$DD02` write for the VIC bank (an earlier version of
|
|
193
|
+
this bullet said "the loader", which read as a rule for every loader).
|
|
194
|
+
- Any KERNAL serial call (`JSR $FFD5`, a `krnio` save) while the drive is in
|
|
195
|
+
loader mode stalls, because the drive is no longer running DOS; call
|
|
196
|
+
`uninstall` first.
|
|
197
|
+
|
|
198
|
+
### Cross-references
|
|
199
|
+
|
|
200
|
+
- KERNAL routine `LOAD` (`$FFD5` → `$F49E` → `JMP ($0330)`), RAM vector `ILOAD` at `$0330/$0331`, ROM default `$F4A5`
|
|
201
|
+
- KERNAL routines `RESTOR` (`$FF8A`) and `VECTOR` (`$FF8D`) — `../hardware/kernal-routines-reference.md`
|
|
202
|
+
- Technique `krill_loader_integration` — called directly through its own API; this pitfall does not apply to it (see Mechanism)
|
|
203
|
+
- Technique `sparkle_irq_loader` — uses no KERNAL routine; this pitfall does not apply to it
|
|
204
|
+
- Pitfall `fastloader_dd00_write_corrupts_resident` — the hazard that does apply to a resident Krill
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## gcr_timing_assumes_stock_drive — GCR loaders assume stock 1541 timing; non-stock drives fail or corrupt
|
|
209
|
+
|
|
210
|
+
**Severity:** medium
|
|
211
|
+
**Region:** both
|
|
212
|
+
**Triggered by techniques:** sparkle_irq_loader, krill_loader_integration, disk_protection_tricks, iffl_single_file, drive_code_upload_and_job_queue
|
|
213
|
+
|
|
214
|
+
### Symptom
|
|
215
|
+
|
|
216
|
+
A demo that loads cleanly on a real stock 1541 hangs indefinitely on an SD2IEC,
|
|
217
|
+
corrupts data silently on a 1571 running in 1541-compatibility mode, produces
|
|
218
|
+
random read errors on a JiffyDOS-modified 1541, or loads correctly on PAL but
|
|
219
|
+
not on NTSC. The loading symptom looks like a hardware fault — the drive's
|
|
220
|
+
activity LED may flash abnormally, or the C64 may freeze at the loading screen
|
|
221
|
+
with no visible error. On SD2IEC the LED typically blinks in an error pattern;
|
|
222
|
+
on a JiffyDOS 1541 the machine may hang at the first block receive with the
|
|
223
|
+
drive motor spinning continuously.
|
|
224
|
+
|
|
225
|
+
The symptom is drive-dependent and consistently reproducible: the same disk image
|
|
226
|
+
that fails on SD2IEC loads correctly every time on a real 1541.
|
|
227
|
+
|
|
228
|
+
### Mechanism
|
|
229
|
+
|
|
230
|
+
GCR-level fast loaders — Krill, Sparkle, and similar — bypass the KERNAL's IEC
|
|
231
|
+
serial routines entirely. Instead of calling `IECIN` or `IECOUT`, they install
|
|
232
|
+
custom drive-side code (via `M-W`/`M-E` commands) and then communicate with that
|
|
233
|
+
code using direct bit-banging of CIA2 `$DD00` on the C64 side. The C64-side
|
|
234
|
+
receive loop is a tight, cycle-counted loop. Each iteration tests a specific bit
|
|
235
|
+
of `$DD00` (the CLK line, bit 6, or the DATA line, bit 7) and waits for a
|
|
236
|
+
transition within a hard cycle-count window.
|
|
237
|
+
|
|
238
|
+
The drive-side code sends each byte at a cadence tuned to the stock 1541's 1 MHz
|
|
239
|
+
6502 clock (1,000,000 cycles per second at the drive's internal clock rate). The
|
|
240
|
+
C64-side loop expects each bit transition to arrive within a window of
|
|
241
|
+
approximately 4-6 µs. If the drive's bit timing deviates — because it is running
|
|
242
|
+
at a different clock speed, because it has different VIA peripheral chip
|
|
243
|
+
characteristics, or because its protocol is JiffyDOS rather than the Krill
|
|
244
|
+
protocol — the C64-side loop times out. Depending on the loader's error handling,
|
|
245
|
+
a timeout either hangs (spin loop) or returns a garbled byte.
|
|
246
|
+
|
|
247
|
+
Common non-stock configurations and why each fails:
|
|
248
|
+
|
|
249
|
+
**SD2IEC.** An SD card reader that emulates 1541 filesystem operations at the
|
|
250
|
+
Commodore DOS level. It speaks the standard KERNAL IEC serial protocol but does
|
|
251
|
+
not emulate GCR at the hardware level at all. `M-W`/`M-E` commands either return
|
|
252
|
+
an error or are silently ignored, so Krill's drive-side code never installs. The
|
|
253
|
+
C64 side then tries to do the fast handshake with a device that has no idea what
|
|
254
|
+
the fast protocol is, and the bus hangs.
|
|
255
|
+
|
|
256
|
+
**1571 in 1541-compatibility mode.** The 1571 can be addressed as device 8 in
|
|
257
|
+
1541 mode, but its VIA chip timings and its internal bus arbitration differ from
|
|
258
|
+
the 1541. Krill's drive-side timing constants were measured on 1541 hardware; the
|
|
259
|
+
1571's slightly different VIA propagation delays shift the bit window outside the
|
|
260
|
+
C64-side tolerance. This causes occasional bit errors that corrupt the loaded data
|
|
261
|
+
in ways that are not immediately obvious — the program may start but behave
|
|
262
|
+
incorrectly because a few bytes of code or data were flipped.
|
|
263
|
+
|
|
264
|
+
**JiffyDOS-modified 1541.** JiffyDOS replaces the 1541 ROM with a ROM that
|
|
265
|
+
implements the JiffyDOS burst protocol. When Krill's drive-side code is uploaded
|
|
266
|
+
via `M-W`/`M-E` and executed, it overwrites the JiffyDOS RAM driver in the
|
|
267
|
+
drive's RAM workspace. This usually causes Krill to work correctly on a JiffyDOS
|
|
268
|
+
machine — but only if the Krill version's timing constants were compiled for the
|
|
269
|
+
stock 1541 MHz clock, and only if the JiffyDOS kernel does not re-initialize the
|
|
270
|
+
RAM workspace between command-channel operations. Some JiffyDOS revisions
|
|
271
|
+
periodically restore their RAM workspace, which can corrupt the in-place Krill
|
|
272
|
+
drive code during a multi-part load sequence.
|
|
273
|
+
|
|
274
|
+
**NTSC timing.** The C64-side receive loop's cycle counts are valid at PAL's
|
|
275
|
+
0.985 MHz system clock. NTSC runs at 1.022 MHz — approximately 3.8% faster.
|
|
276
|
+
Each of the four 18-cycle handshake phases in Krill's receive loop resynchronises
|
|
277
|
+
on an ATN edge, so the drift does not accumulate across a byte; what matters is
|
|
278
|
+
the fixed gap of about 10 C64 cycles between toggling ATN and reading the bus,
|
|
279
|
+
which the 1541 (whose 1 MHz clock does not change with the video standard) needs
|
|
280
|
+
up to 14 of its own cycles to beat. At 3.8% that gap shrinks by well under one
|
|
281
|
+
cycle — Krill's own `NTSC_COMPATIBILITY` build restores it by adding exactly one
|
|
282
|
+
cycle to each phase — which is enough to push a phase already at the edge of its
|
|
283
|
+
window over it. (An earlier version of this page said the shift was "roughly 3-4
|
|
284
|
+
cycles" per window; 3.8% of an 18-cycle phase is 0.7 cycles, and of the whole
|
|
285
|
+
72-cycle byte 2.7 — rung 3 from the phase lengths in
|
|
286
|
+
`../techniques/loaders-packers.md` "Cycle budget".) Krill's build has an NTSC
|
|
287
|
+
switch — the `NTSC_COMPATIBILITY` define in `loaderconfig.inc` (the config file
|
|
288
|
+
selected with `EXTCONFIGPATH=`, or `include/config.inc`) of its cc65/ca65 build;
|
|
289
|
+
it is not a KickAssembler `-D` flag, and it is not a `make` command-line variable
|
|
290
|
+
either (the Makefile does not forward one to ca65). An earlier version of this
|
|
291
|
+
page said `-DNTSC=1`. Enabling it pads the C64-side transfer loop by one cycle at
|
|
292
|
+
each ATN sync point (and one NOP in the send routine) so a single resident works
|
|
293
|
+
on PAL and NTSC at a small PAL-speed cost; PAL/NTSC is not auto-detected by the
|
|
294
|
+
installer and a PAL-only resident gives no error on NTSC. Using the PAL binary on
|
|
295
|
+
NTSC hardware is reliable enough on fast, well-terminated hardware but causes
|
|
296
|
+
errors on borderline machines.
|
|
297
|
+
|
|
298
|
+
### Fix
|
|
299
|
+
|
|
300
|
+
**Detect the drive before installing the fast loader.** Ask the DOS for its own
|
|
301
|
+
version string. `M-R` on the command channel returns raw bytes of drive memory,
|
|
302
|
+
and the 1541's power-on message `CBM DOS V2.6 1541` sits in ROM at
|
|
303
|
+
`$E5B7`–`$E5C7`, so the four bytes from `$E5C4` read `1541` — the last of them
|
|
304
|
+
`$B1`, `'1'` with bit 7 set, which is the message table's end marker, so strip
|
|
305
|
+
bit 7 before comparing. A 1571 answers `1571` from the same address, a 1581
|
|
306
|
+
answers `$FF $FF $FF $FF` (its ROM has nothing there), and what an SD2IEC answers
|
|
307
|
+
was not measured here. `1541` at `$E5C4` is rung 1 from the two 1541 ROM images
|
|
308
|
+
VICE 3.10 ships (325302-01+901229-05 and the 1541-II's 251968-03); the
|
|
309
|
+
behaviour is rung 1 in VICE x64sc 3.10 with `-drive8truedrive` on drive types
|
|
310
|
+
1541, 1541-II, 1571 and 1581, where the routine below returned carry clear for
|
|
311
|
+
the 1541 and carry set for the 1571. What the test identifies is the firmware,
|
|
312
|
+
not the mechanism. A 1540 — the same mechanism, older DOS — answers `V170` from
|
|
313
|
+
that address (rung 1, its ROM image), and a 1541 whose ROM has been replaced
|
|
314
|
+
(JiffyDOS, SpeedDOS, Dolphin DOS) will not say `1541` there either (rung 4; no
|
|
315
|
+
such ROM ships with VICE), so the routine sends both down the KERNAL path. That
|
|
316
|
+
is the safe side to fail on, but note it is stricter than the Mechanism paragraph
|
|
317
|
+
above, which says Krill's drive code usually runs on a JiffyDOS 1541. The table of
|
|
318
|
+
images and addresses is in `../formats/iec-disk-reference.md`, "Identifying the
|
|
319
|
+
drive over the command channel".
|
|
320
|
+
|
|
321
|
+
```kick
|
|
322
|
+
jsr detect_1541
|
|
323
|
+
bcc install_krill
|
|
324
|
+
jmp use_kernal_load // anything that is not a 1541: KERNAL path
|
|
325
|
+
|
|
326
|
+
// Carry clear on return: the drive says "1541" at $E5C4. Carry set: anything
|
|
327
|
+
// else, including a drive that never answered.
|
|
328
|
+
detect_1541:
|
|
329
|
+
lda #15
|
|
330
|
+
ldx #8
|
|
331
|
+
ldy #15
|
|
332
|
+
jsr $ffba // SETLFS 15,8,15
|
|
333
|
+
lda #cmd_end-cmd
|
|
334
|
+
ldx #<cmd
|
|
335
|
+
ldy #>cmd
|
|
336
|
+
jsr $ffbd // SETNAM: the command text goes where a filename would
|
|
337
|
+
jsr $ffc0 // OPEN sends "M-R" $C4 $E5 $04
|
|
338
|
+
bcs no_answer
|
|
339
|
+
ldx #15
|
|
340
|
+
jsr $ffc6 // CHKIN channel 15
|
|
341
|
+
ldy #0
|
|
342
|
+
read:
|
|
343
|
+
jsr $ffcf // CHRIN: one reply byte per call
|
|
344
|
+
and #$7f // the last byte of the ROM string has bit 7 set
|
|
345
|
+
sta reply,y
|
|
346
|
+
iny
|
|
347
|
+
cpy #4
|
|
348
|
+
bne read
|
|
349
|
+
jsr $ffcc // CLRCHN
|
|
350
|
+
lda #15
|
|
351
|
+
jsr $ffc3 // CLOSE
|
|
352
|
+
ldy #3
|
|
353
|
+
compare:
|
|
354
|
+
lda reply,y
|
|
355
|
+
cmp expect,y
|
|
356
|
+
bne mismatch
|
|
357
|
+
dey
|
|
358
|
+
bpl compare
|
|
359
|
+
clc
|
|
360
|
+
rts
|
|
361
|
+
mismatch:
|
|
362
|
+
sec
|
|
363
|
+
rts
|
|
364
|
+
no_answer:
|
|
365
|
+
lda #15
|
|
366
|
+
jsr $ffc3
|
|
367
|
+
sec
|
|
368
|
+
rts
|
|
369
|
+
cmd:
|
|
370
|
+
.byte $4d, $2d, $52 // "M-R" in PETSCII
|
|
371
|
+
.byte $c4, $e5 // $E5C4, low byte first
|
|
372
|
+
.byte $04 // four bytes
|
|
373
|
+
cmd_end:
|
|
374
|
+
expect:
|
|
375
|
+
.byte $31, $35, $34, $31 // "1541"
|
|
376
|
+
reply:
|
|
377
|
+
.byte 0, 0, 0, 0
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Correction (2026-09-21).** The earlier text expected `$41` at `$E5C3` and called
|
|
381
|
+
it "the DOS version byte". `$E5C3` is `$20` — the space between `V2.6` and `1541`
|
|
382
|
+
— in every 1541-family ROM VICE ships (1540, 1541, 1541-II, 1570, 1571), so that
|
|
383
|
+
test never matched and the fast path was never installed. `$41` ("A") is the
|
|
384
|
+
DOS-version marker at offset 2 of the BAM sector, track 18 sector 0, which the
|
|
385
|
+
format routine writes to the disk; the ROM keeps that constant at `$FED5`, not at
|
|
386
|
+
`$E5C3`.
|
|
387
|
+
|
|
388
|
+
**Provide a KERNAL fallback branch.** The most robust approach is to ship two
|
|
389
|
+
load paths: the GCR fast path for stock 1541 hardware, and a `JSR $FFD5` KERNAL
|
|
390
|
+
LOAD fallback for everything else. Detect the drive at startup, set a flag, and
|
|
391
|
+
branch on the flag at each load call. This adds ~50 bytes of overhead but
|
|
392
|
+
eliminates drive-compatibility bugs from the entire production.
|
|
393
|
+
|
|
394
|
+
Krill's Loader has this built in: set `LOAD_VIA_KERNAL_FALLBACK=1` in
|
|
395
|
+
`loaderconfig.inc` and the loader transparently falls back to the KERNAL load
|
|
396
|
+
path when drive-code installation fails (incompatible drive such as SD2IEC, or
|
|
397
|
+
true-drive emulation disabled) — no hand-rolled detection branch needed. The
|
|
398
|
+
cost is a larger host-side stub and KERNAL-speed loading on those devices; see
|
|
399
|
+
`../techniques/loaders-packers.md` "v194 concrete integration reference".
|
|
400
|
+
|
|
401
|
+
**Compile NTSC binaries separately.** If the production targets both PAL and NTSC,
|
|
402
|
+
compile the Krill C64-side stub twice with the appropriate clock constant and
|
|
403
|
+
select the right binary at startup based on the CIA timer reading (standard PAL/
|
|
404
|
+
NTSC detection: count CIA1 timer ticks per VBL interrupt; PAL = 19,656 cycles,
|
|
405
|
+
NTSC = 17,095 on the 6567R8 (16,768 on the older 6567R56A) — a 15% difference
|
|
406
|
+
that is easy to detect reliably within a single frame. An earlier version of this
|
|
407
|
+
page gave NTSC as ~16,715, which is the NTSC frame time in microseconds, not its
|
|
408
|
+
cycle count; 65 × 263 = 17,095 and 64 × 262 = 16,768, rung 3, matching
|
|
409
|
+
`../hardware/pal-ntsc-reference.md`).
|
|
410
|
+
|
|
411
|
+
### Cross-references
|
|
412
|
+
|
|
413
|
+
- Technique `sparkle_irq_loader` — PAL and NTSC, plugins included (Sparkle 3.4 manual p. 3; an earlier version of this line said PAL-only by default with NTSC needing timing constants)
|
|
414
|
+
- Technique `krill_loader_integration` — `NTSC_COMPATIBILITY` config define, drive detection discussion
|
|
415
|
+
- `docs/formats/iec-disk-reference.md` — IEC bus signal levels, timing diagrams, 1541 GCR zones
|
|
416
|
+
|
|
417
|
+
**Sources for the Fix.** The drive ROM images VICE 3.10 ships in
|
|
418
|
+
`/opt/homebrew/opt/vice/share/vice/DRIVES/` and the KERNAL 901227-03 image, read
|
|
419
|
+
byte by byte (rung 1); VICE x64sc 3.10 with `-drive8truedrive` for the runs
|
|
420
|
+
(rung 1); `../formats/iec-disk-reference.md` for the per-image table.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## basic_stub_collision_with_data — BASIC SYS stub at $0801 overlaps program data
|
|
425
|
+
|
|
426
|
+
**Severity:** medium
|
|
427
|
+
**Region:** both
|
|
428
|
+
**Triggered by kernal:** LOAD
|
|
429
|
+
**Triggered by techniques:** crunched_data_in_basic_stub, exomizer_basics
|
|
430
|
+
|
|
431
|
+
### Symptom
|
|
432
|
+
|
|
433
|
+
A program loads without error. Typing `RUN` causes BASIC to execute the SYS stub
|
|
434
|
+
and launch the machine code as expected — but the machine code immediately
|
|
435
|
+
behaves incorrectly: the first few reads from the data area return garbage values,
|
|
436
|
+
a lookup table produces wrong results, a sprite shape read from `$0801` produces
|
|
437
|
+
a corrupt sprite, or a character set starting at `$0800` is garbled in its first
|
|
438
|
+
glyph. The bug manifests only when the program is launched from BASIC via `RUN`;
|
|
439
|
+
loading with a custom autostart that jumps directly to the entry point (bypassing
|
|
440
|
+
the BASIC stub execution) produces correct behavior. The data corruption is
|
|
441
|
+
in the first 12 bytes of the program's data area (13 where the assembler pads the
|
|
442
|
+
stub so code starts at `$080E`, as KickAssembler's `BasicUpstart2` does), which
|
|
443
|
+
coincides with the BASIC stub's footprint.
|
|
444
|
+
|
|
445
|
+
A second pattern: a program that places data at `$0801` and tests it at startup
|
|
446
|
+
reads the correct data in the assembler's simulation but reads BASIC stub bytes
|
|
447
|
+
on real hardware or in VICE, because the programmer forgot that the loaded PRG
|
|
448
|
+
begins with the stub at `$0801` — the data they assembled at `$0801` was
|
|
449
|
+
overwritten by the stub in the final PRG layout.
|
|
450
|
+
|
|
451
|
+
### Mechanism
|
|
452
|
+
|
|
453
|
+
A C64 PRG file that auto-starts via BASIC begins at address `$0801`. The BASIC
|
|
454
|
+
interpreter's program area starts at `$0801`. The canonical autostart stub
|
|
455
|
+
occupies the first 12 bytes of that area (an earlier version of this sentence
|
|
456
|
+
said 13; the table below and the Option B listing's `cpx #12` both count 12):
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
Address Byte Meaning
|
|
460
|
+
$0801 $0B Link to next BASIC line (lo): $0801 + $0A = $080B
|
|
461
|
+
$0802 $08 Link to next BASIC line (hi)
|
|
462
|
+
$0803 $0A Line number lo: 10
|
|
463
|
+
$0804 $00 Line number hi
|
|
464
|
+
$0805 $9E BASIC token for SYS
|
|
465
|
+
$0806 $32 PETSCII '2'
|
|
466
|
+
$0807 $30 PETSCII '0'
|
|
467
|
+
$0808 $36 PETSCII '6'
|
|
468
|
+
$0809 $34 PETSCII '4'
|
|
469
|
+
$080A $00 End of BASIC line
|
|
470
|
+
$080B $00 End of BASIC program (link lo = 0)
|
|
471
|
+
$080C $00 End of BASIC program (link hi = 0)
|
|
472
|
+
$080D ... First byte of machine code (entry point = 2061 decimal)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
This layout means that `$0801` through `$080C` (inclusive — 12 bytes) belong to
|
|
476
|
+
BASIC, and the machine code proper begins at `$080D`. Any data the programmer
|
|
477
|
+
places at addresses `$0801` through `$080C` will be overwritten by the stub
|
|
478
|
+
bytes above when the final PRG is assembled with the standard BASIC autostart.
|
|
479
|
+
|
|
480
|
+
The collision arises in two distinct ways:
|
|
481
|
+
|
|
482
|
+
**Collision type A — data intended to reside at $0801.** The programmer explicitly
|
|
483
|
+
places a data table, sprite shape, or character ROM copy at `$0801`, intending to
|
|
484
|
+
use it from machine code. Because the PRG loads to `$0801` and the BASIC stub
|
|
485
|
+
occupies the first 12 bytes, those 12 bytes of the programmer's data are replaced
|
|
486
|
+
by BASIC stub bytes. The machine code reads `$9E $32 $30 $36 $34 $00 ...` (the
|
|
487
|
+
SYS token and address digits) instead of the intended data values.
|
|
488
|
+
|
|
489
|
+
**Collision type B — SYS target address mismatch with data placement.** The
|
|
490
|
+
programmer adjusts the SYS address in the stub (e.g., changing `SYS 2064` to
|
|
491
|
+
`SYS 2061` or `SYS 2080`) to accommodate a slightly longer or shorter header
|
|
492
|
+
sequence, but does not correspondingly move the data that follows. If the SYS
|
|
493
|
+
target is `2064` (`$0810`) but data is placed starting at `$080D`, the region
|
|
494
|
+
`$080D`-`$0810` is ambiguously both stub and data and will contain the stub's
|
|
495
|
+
trailing null bytes.
|
|
496
|
+
|
|
497
|
+
Note that `SYS 2064` points to `$0810`, not `$080D`. The difference of three
|
|
498
|
+
bytes is the end-of-program null word (`$0000`) plus one byte of alignment in
|
|
499
|
+
some assembler output. Different assemblers and different stub templates produce
|
|
500
|
+
slightly different layouts; the canonical addresses to check are wherever the
|
|
501
|
+
PRG assembles the stub and wherever the PRG's data begins — if they overlap, the
|
|
502
|
+
collision exists regardless of which specific addresses are involved.
|
|
503
|
+
|
|
504
|
+
### Fix
|
|
505
|
+
|
|
506
|
+
Three options, each with different trade-offs:
|
|
507
|
+
|
|
508
|
+
**Option A — Move data after the stub (preferred for new code).**
|
|
509
|
+
|
|
510
|
+
Place all data that needs to survive program startup at `$0820` or higher. Leave
|
|
511
|
+
`$0801`-`$081F` as BASIC stub + entry code. This is the safe default layout. The
|
|
512
|
+
cost is 31 bytes of address space before the programmer's first data byte, which
|
|
513
|
+
is negligible in any context except a 256-byte intro.
|
|
514
|
+
|
|
515
|
+
```kick
|
|
516
|
+
* = $0801 // BASIC stub at $0801
|
|
517
|
+
.byte $0B, $08 // link to $080B
|
|
518
|
+
.byte $0A, $00 // line 10
|
|
519
|
+
.byte $9E // SYS token
|
|
520
|
+
.text "2061" // SYS target = $080D
|
|
521
|
+
.byte $00 // end of line
|
|
522
|
+
.byte $00, $00 // end of BASIC program
|
|
523
|
+
|
|
524
|
+
* = $080D // machine code entry
|
|
525
|
+
entry:
|
|
526
|
+
jmp main
|
|
527
|
+
|
|
528
|
+
* = $0820 // data safely after stub
|
|
529
|
+
lookup_table:
|
|
530
|
+
.byte $00, $01, $04, $09, $10, $19, $24, $31 // square table, safe here
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Option B — Overwrite the stub at runtime (for productions that never use `RUN`
|
|
534
|
+
again after launch).**
|
|
535
|
+
|
|
536
|
+
The BASIC stub is only needed once: for the initial `RUN` that invokes `SYS`.
|
|
537
|
+
After `SYS` transfers control to machine code, the stub bytes at `$0801`-`$080C`
|
|
538
|
+
are dead — BASIC is no longer running and the BASIC program area is not used by
|
|
539
|
+
the machine code. The entry code can immediately overwrite those bytes with real
|
|
540
|
+
data:
|
|
541
|
+
|
|
542
|
+
```kick
|
|
543
|
+
* = $0801
|
|
544
|
+
.byte $0B, $08, $0A, $00, $9E // SYS stub start
|
|
545
|
+
.text "2061"
|
|
546
|
+
.byte $00, $00, $00 // end markers
|
|
547
|
+
|
|
548
|
+
* = $080D
|
|
549
|
+
entry:
|
|
550
|
+
// Overwrite the now-dead BASIC stub area with real data
|
|
551
|
+
ldx #0
|
|
552
|
+
overwrite_loop:
|
|
553
|
+
lda real_data,x
|
|
554
|
+
sta $0801,x
|
|
555
|
+
inx
|
|
556
|
+
cpx #12
|
|
557
|
+
bne overwrite_loop
|
|
558
|
+
// $0801-$080C now contains real_data[0..11]
|
|
559
|
+
jmp main
|
|
560
|
+
|
|
561
|
+
real_data:
|
|
562
|
+
.byte $00, $01, $04, $09, $10, $19, $24, $31, $40, $51, $64, $79
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
This technique is common in 256-byte intros where every available byte of address
|
|
566
|
+
space is used and the stub area must double as a data carrier. (The Option A and
|
|
567
|
+
Option B listings were in capitals until 2026-09-22, which KickAssembler 5.25
|
|
568
|
+
rejects — "Pseudo command JMP not defined" — so neither had ever been assembled;
|
|
569
|
+
both build now.)
|
|
570
|
+
|
|
571
|
+
**Option C — Use a CRT (cartridge) format to bypass the BASIC stub entirely.**
|
|
572
|
+
|
|
573
|
+
A `.CRT` image starts execution from the cartridge reset vector, bypassing BASIC
|
|
574
|
+
and the BASIC stub entirely. The `$0801` area is free for program use from the
|
|
575
|
+
moment the cartridge takes control. This option requires distributing a `.CRT`
|
|
576
|
+
file rather than a `.PRG`, which is appropriate for cartridge releases or EasyFlash
|
|
577
|
+
productions but not for disk-based demos or games that load via KERNAL `LOAD`.
|
|
578
|
+
|
|
579
|
+
**Detecting the collision before runtime.** In KickAssembler, assert that the
|
|
580
|
+
data placement does not overlap the stub using the `.assert` directive:
|
|
581
|
+
|
|
582
|
+
```kick
|
|
583
|
+
.assert "Data must be after BASIC stub", data_start >= $080D, true
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
If data is intended to start at exactly `$080D` (immediately after the stub), also
|
|
587
|
+
ensure the SYS target in the stub matches: `SYS 2061` for entry at `$080D`,
|
|
588
|
+
`SYS 2064` for entry at `$0810`. Mismatches between the SYS operand and the
|
|
589
|
+
actual entry label are a second source of subtle boot failures distinct from the
|
|
590
|
+
data collision described above.
|
|
591
|
+
|
|
592
|
+
### Cross-references
|
|
593
|
+
|
|
594
|
+
- KERNAL routine `LOAD` (`$FFD5`) — the loading mechanism that delivers the PRG to `$0801`
|
|
595
|
+
- `docs/formats/c64-file-formats.md` — PRG file layout, load address header, CRT format
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## krill_cc65_2_18_miscompile — Building Krill v194 with cc65 2.18 silently produces a broken loader
|
|
600
|
+
|
|
601
|
+
**Severity:** high
|
|
602
|
+
**Region:** both
|
|
603
|
+
**Triggered by techniques:** krill_loader_integration
|
|
604
|
+
|
|
605
|
+
### Symptom
|
|
606
|
+
|
|
607
|
+
You build Krill's Loader from source, the build is clean (exit 0, no warnings),
|
|
608
|
+
the blobs are the expected size and disassemble to sane code — but at runtime the
|
|
609
|
+
installed loader does not work: `install` either hangs or returns
|
|
610
|
+
`DEVICE_NOT_PRESENT` (`$FE`), and `loadraw` never succeeds. Meanwhile Krill's own
|
|
611
|
+
*prebuilt* `loadertest-cNN.d64` (shipped in the archive) runs perfectly in the
|
|
612
|
+
same emulator, which makes it look like your integration is at fault when the real
|
|
613
|
+
culprit is the assembler.
|
|
614
|
+
|
|
615
|
+
### Mechanism
|
|
616
|
+
|
|
617
|
+
Krill's Loader (repository version 194, 2022) is developed against cc65 **git
|
|
618
|
+
master**, not the last tagged release. The Homebrew/distro `cc65` is **V2.18**
|
|
619
|
+
(2018) — four years of cc65 codegen changes behind. Some construct in Krill's
|
|
620
|
+
drive/host code is miscompiled by 2.18 in a way that passes assembly cleanly but
|
|
621
|
+
breaks the cycle-exact serial protocol, so the drive never responds. Verified
|
|
622
|
+
2026-05-20: Homebrew cc65 2.18 → broken loader; cc65 git **V2.19** (`cc3c40c`) →
|
|
623
|
+
working loader, from identical Krill sources and config.
|
|
624
|
+
|
|
625
|
+
### Fix
|
|
626
|
+
|
|
627
|
+
Build cc65 from git and use it for the Krill build:
|
|
628
|
+
|
|
629
|
+
```
|
|
630
|
+
git clone --depth 1 https://github.com/cc65/cc65 && make -C cc65
|
|
631
|
+
PATH=<cc65-git>/bin:$PATH CC65_HOME=<cc65-git> make -C loader/src PLATFORM=c64 prg ...
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Validate against Krill's prebuilt `loadertest` first: if the prebuilt works in
|
|
635
|
+
your emulator but your freshly-built loader does not, suspect the assembler before
|
|
636
|
+
your integration. Also note `make-loadersymbolsinc.pl` calls `grep -P`, so on
|
|
637
|
+
macOS put GNU grep ahead of BSD grep or the symbol-file step errors.
|
|
638
|
+
|
|
639
|
+
### Cross-references
|
|
640
|
+
|
|
641
|
+
- Technique `krill_loader_integration` — full v194 build + integration reference
|
|
642
|
+
- `docs/techniques/loaders-packers.md` "v194 concrete integration reference (cc65 build)"
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## fastloader_resident_in_kernal_workspace — Fast-loader resident at $0200-$03FF collides with KERNAL tables; install hangs
|
|
647
|
+
|
|
648
|
+
**Severity:** high
|
|
649
|
+
**Region:** both
|
|
650
|
+
**Triggered by techniques:** krill_loader_integration
|
|
651
|
+
|
|
652
|
+
### Symptom
|
|
653
|
+
|
|
654
|
+
A fast loader whose resident/host code is placed low in RAM (e.g. `RESIDENT=$0200`)
|
|
655
|
+
hangs the moment `install` runs — before the first load. Move the resident
|
|
656
|
+
elsewhere and install completes normally.
|
|
657
|
+
|
|
658
|
+
### Mechanism
|
|
659
|
+
|
|
660
|
+
`install` opens the drive command channel (KERNAL `SETLFS`/`SETNAM`/`OPEN`) to
|
|
661
|
+
upload the drive code via `M-W`. KERNAL `OPEN` writes the logical-file tables
|
|
662
|
+
(LAT/FAT/SAT) at **`$0259-$0276`**, and `$0200-$0258` is the BASIC/KERNAL input
|
|
663
|
+
buffer. A resident placed at `$0200` (e.g. `$0200-$02EC`) overlaps that workspace;
|
|
664
|
+
the KERNAL OPEN corrupts the resident (or vice-versa) mid-install, so the
|
|
665
|
+
handshake never completes. The address looks "free" because nothing visible uses
|
|
666
|
+
it once BASIC is out of the way — but the KERNAL serial path does.
|
|
667
|
+
|
|
668
|
+
### Fix
|
|
669
|
+
|
|
670
|
+
Place the resident outside KERNAL/BASIC zero-page-adjacent workspace. The free
|
|
671
|
+
4 KB block at `$C000-$CFFF` works well: put the (transient) installer at `$C000`
|
|
672
|
+
and the (persistent) resident just above it (e.g. `$CD00`). Verified 2026-05-20:
|
|
673
|
+
`RESIDENT=$0200` hung install; `RESIDENT=$CD00` fixed it.
|
|
674
|
+
|
|
675
|
+
### Cross-references
|
|
676
|
+
|
|
677
|
+
- Technique `krill_loader_integration` — "Embedding into an Oscar64 program"
|
|
678
|
+
- `docs/hardware/c64-memory-map.md` — page 2/3 KERNAL workspace ($0200-$03FF)
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## fastloader_dd00_write_corrupts_resident — Raw $DD00 writes (VIC bank switch) while a Krill or Sparkle loader is resident corrupt its bus-lock
|
|
683
|
+
|
|
684
|
+
**Severity:** high
|
|
685
|
+
**Region:** both
|
|
686
|
+
**Triggered by registers:** DD00
|
|
687
|
+
**Triggered by techniques:** krill_loader_integration, sparkle_irq_loader
|
|
688
|
+
|
|
689
|
+
### Symptom
|
|
690
|
+
|
|
691
|
+
A GCR fast loader is installed and one load works. Then the program switches VIC
|
|
692
|
+
graphics mode or VIC bank (anything that writes CIA2 `$DD00`) — e.g. a bitmap
|
|
693
|
+
title screen toggling to a text play screen — and the *next* `loadraw` (or
|
|
694
|
+
`uninstall`) hangs. The first load worked, so the loader looks fine until the mode
|
|
695
|
+
switch; the hang then looks unrelated to graphics.
|
|
696
|
+
|
|
697
|
+
### Mechanism
|
|
698
|
+
|
|
699
|
+
`$DD00` is shared: bits 0-1 select the VIC bank, bit 2 is the user-port RS-232 TXD
|
|
700
|
+
line, and bits 3-7 are the IEC bus lines the loader bit-bangs (3-5 ATN/CLK/DATA
|
|
701
|
+
out, 6-7 CLK/DATA in — an earlier version of this page said bits 2-7; see
|
|
702
|
+
`../hardware/cia-reference.md` §`$DD00`). While the loader is installed it owns the IEC bits and keeps a
|
|
703
|
+
"bus-lock" state in them. A raw write of the whole `$DD00` byte — exactly what a
|
|
704
|
+
VIC-bank set does, including Oscar64's `vic_setmode()` and any
|
|
705
|
+
`STA $DD00` / `LDA #v:STA $DD00` — overwrites the IEC bits with values the loader
|
|
706
|
+
did not expect, desyncing the drive protocol. The next drive op then waits forever.
|
|
707
|
+
For Krill, a read-modify-write that changes *only* bits 0-1 while the loader is
|
|
708
|
+
idle is tolerated; a full-byte write, or any write while the loader is
|
|
709
|
+
mid-transfer, is not. Other loaders publish different rules, some the reverse of
|
|
710
|
+
Krill's; the table under Fix gives each. (An earlier version of this page stated
|
|
711
|
+
Krill's rule as the rule for every resident loader.)
|
|
712
|
+
|
|
713
|
+
### Fix
|
|
714
|
+
|
|
715
|
+
Don't keep the loader resident across VIC mode/bank switches. Two options:
|
|
716
|
+
|
|
717
|
+
1. **Lazy install (robust, verified):** install only around each load batch
|
|
718
|
+
(probe / level load / load-game), `uninstall` immediately after, then re-assert
|
|
719
|
+
the VIC bank with a read-modify-write (`$DD00 = ($DD00 & $FC) | bank`). With the
|
|
720
|
+
loader uninstalled by default, the drive sits in DOS, so saves are plain KERNAL
|
|
721
|
+
`krnio` with no uninstall dance and no `vic_setmode` ever runs inside the
|
|
722
|
+
install→loads→uninstall window. Cost: one drive-code upload per load batch —
|
|
723
|
+
fine for infrequent loads. (Tideline ships this, VICE-verified 2026-05-20.)
|
|
724
|
+
2. If the loader must stay resident, switch the bank the way that loader
|
|
725
|
+
documents. None of these was run here; each rule is from the loader's own
|
|
726
|
+
documentation (rung 4 here).
|
|
727
|
+
|
|
728
|
+
| Loader | VIC bank switch while resident | Arbitrary `$DD00` values |
|
|
729
|
+
|---|---|---|
|
|
730
|
+
| Krill | `SET_VIC_BANK`; never a raw `$DD00` write | `ENTER_BUS_LOCK` / `LEAVE_BUS_LOCK` |
|
|
731
|
+
| Sparkle 3.4 | Do not write `$DD00`: the loader may read it as a drive command and reset the drive. Write `LDA #$3C+bank : STA $DD02`, bank 0-3 (manual pp. 20-21; common issue 1, p. 29). | "Direct bus lock": `$03` (bits 3-5 clear) to `$DD02`, then any `$DD00` value; restore `$DD00` to `$38` first, then `$DD02` to `$3C`+bank (pp. 22-23). |
|
|
732
|
+
| Bitfire | Plain stores of `$00`-`$03` to `$DD00` "at any time, also while loading"; not a read-modify-write (`LDA $DD00 : AND #$FC : ORA #bank`) (readme, "Bank switching") | Only while idle, between the `bus_lock` and `bus_unlock` macros |
|
|
733
|
+
|
|
734
|
+
Sparkle also accepts any `$DD02` value between loader calls, as long as
|
|
735
|
+
`$3C`+bank is back before the next call (the "indirect bus lock", pp. 21-22).
|
|
736
|
+
Why the `$DD02` write selects the bank (not stated in the manual; rung 4):
|
|
737
|
+
the loader leaves `$DD00` bits 0-1 at 0, so a bit set to output drives 0 and a
|
|
738
|
+
bit set to input floats to 1, and `$3C`+bank gives the inverted bank value the
|
|
739
|
+
VIC reads.
|
|
740
|
+
|
|
741
|
+
**Sources.** Sparkle 3.4 user manual, Sparta (OMG), chapters "Switching VIC
|
|
742
|
+
banks", "Bus lock" and "Common issues", `manual/` in
|
|
743
|
+
https://github.com/spartaomg/SparkleCPP. Bitfire `readme.txt`, sections "Bank
|
|
744
|
+
switching" and the macro list, https://github.com/bboxy/bitfire. The Krill row
|
|
745
|
+
is unchanged from earlier versions of this page.
|
|
746
|
+
|
|
747
|
+
### Cross-references
|
|
748
|
+
|
|
749
|
+
- Technique `krill_loader_integration` — VIC-bank / bus-lock protocol; lazy-install recipe
|
|
750
|
+
- Technique `sparkle_irq_loader` — bank switch through `$DD02`, direct and indirect bus lock
|
|
751
|
+
- Register `$DD00` (CIA2) — VIC bank select bits 0-1 vs IEC lines bits 3-7
|
|
752
|
+
- Pitfall `fastloader_kernal_dependency` — the other "first load works, later loads break" trap
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## d64_error_byte_is_a_controller_code — The D64 error byte is the 1541 job code, not the DOS error number
|
|
757
|
+
|
|
758
|
+
**Severity:** medium
|
|
759
|
+
**Region:** both
|
|
760
|
+
**Triggered by techniques:** disk_protection_tricks, drive_code_upload_and_job_queue
|
|
761
|
+
|
|
762
|
+
### Symptom
|
|
763
|
+
|
|
764
|
+
A tool, a loader's own D64 reader or a page reads the 683-byte error block appended to a `.d64` as DOS error numbers. It then reports "error 3" for a sector the drive would report as `21, READ ERROR`, or looks for the value `21` in the block and never finds it, so a protected disk's bad-sector signature is invisible to it. The other way round, a tool that writes the DOS number into the block produces an image whose byte `21` (`$15`) means nothing to the drive ROM or to VICE, and the protection check that expected a read error gets `0, OK`.
|
|
765
|
+
|
|
766
|
+
### Mechanism
|
|
767
|
+
|
|
768
|
+
The 1541 has two processors' worth of logic in one 6502: the floppy controller side runs sector jobs and hands back a one-byte return code, and the DOS side turns that code into the number and text on the error channel. The D64 error block stores the controller's return code, one byte per sector in track then sector order. `$01` is a clean read. The codes an image can carry, and what the error channel prints for each, measured on the windowless x64sc build of VICE 3.10 with `-drive8truedrive -drive8type 1541` and a `U1` block read of each flagged sector:
|
|
769
|
+
|
|
770
|
+
| Byte | Controller condition | DOS number | Measured on VICE 3.10 |
|
|
771
|
+
|------|----------------------|-----------|-----------------------|
|
|
772
|
+
| `$01` | OK | none | `0, OK` |
|
|
773
|
+
| `$02` | header not found | 20 | `20, READ ERROR` |
|
|
774
|
+
| `$03` | no sync | 21 | `20, READ ERROR` for one flagged sector; `21, READ ERROR` when the whole track is flagged |
|
|
775
|
+
| `$04` | data block not present | 22 | `22, READ ERROR` |
|
|
776
|
+
| `$05` | data checksum error | 23 | `23, READ ERROR` |
|
|
777
|
+
| `$07` | verify error | 25 | `0, OK` (not exercised by a read) |
|
|
778
|
+
| `$08` | write protect on | 26 | `0, OK` (not exercised by a read) |
|
|
779
|
+
| `$09` | header checksum error | 27 | `20, READ ERROR` |
|
|
780
|
+
| `$0B` | disk ID mismatch | 29 | `20, READ ERROR` for one flagged sector; `29, DISK ID MISMATCH` when the whole track is flagged |
|
|
781
|
+
| `$0F` | drive not ready | 74 | `0, OK` |
|
|
782
|
+
|
|
783
|
+
VICE 3.10 honours `$02`, `$03`, `$04`, `$05`, `$09` and `$0B` by building the sector's GCR with the named field spoiled (`gcr.c` in its source: sync bytes, header block ID, data block ID, the two checksums, the header's disk ID). It ignores `$07`, `$08` and `$0F`, so a read of such a sector returns `0, OK`. Codes `$0A` and `$10` were not measured here.
|
|
784
|
+
|
|
785
|
+
Two rows say the printed number depends on how many sectors on the track are flagged. That is the drive ROM's doing, not the emulator's: VICE spoils only the flagged sector, and a ROM that cannot find the header it wants reports `20` whether the header is missing, has a wrong checksum or has a wrong ID. `21` (no sync at all) and `29` (ID mismatch) appear when every sector on the track carries the code. A first version of this measurement put all the flags on track 1 and read `29` for the `$02` and `$05` sectors and `20` for the `$0B` one: a wrong-ID header on the same track leaks into the ROM's per-track seek. Keep flagged sectors on separate tracks when you test an image tool.
|
|
786
|
+
|
|
787
|
+
### Fix
|
|
788
|
+
|
|
789
|
+
Convert at the boundary, in both directions, and name which side a variable holds. The drive ROM's rule (see `../formats/iec-disk-reference.md`, "The 1541 DOS Error Codes") is: take the low four bits; `0` gives `24`; `15` gives `74`; anything else is ORed with `$20` and decremented twice, read as a decimal number. So `$03` gives `$23 - 2 = $21`, printed as `21`. Treat `$00` and `$01` as no error when reading an image, and write `$01` for a clean sector.
|
|
790
|
+
|
|
791
|
+
### Worked example
|
|
792
|
+
|
|
793
|
+
Bad pattern, a Python image reader that prints the block as DOS numbers:
|
|
794
|
+
|
|
795
|
+
```text
|
|
796
|
+
errs = img[174848:] # 683 bytes after the sector data
|
|
797
|
+
for i, b in enumerate(errs):
|
|
798
|
+
if b:
|
|
799
|
+
print(f"sector {i}: error {b}") # prints "error 3"; the drive says 21
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
Fixed, with the ROM's conversion:
|
|
803
|
+
|
|
804
|
+
```text
|
|
805
|
+
def dos_number(job):
|
|
806
|
+
job &= 0x0F
|
|
807
|
+
if job in (0, 1):
|
|
808
|
+
return 0 if job == 1 else 24
|
|
809
|
+
if job == 15:
|
|
810
|
+
return 74
|
|
811
|
+
return int(f"{(job | 0x20) - 2:x}") # $23 - 2 = $21 -> 21
|
|
812
|
+
|
|
813
|
+
errs = img[174848:]
|
|
814
|
+
for i, b in enumerate(errs):
|
|
815
|
+
if b not in (0, 1):
|
|
816
|
+
print(f"sector {i}: job {b:#04x} -> DOS {dos_number(b)}")
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
The measurement itself. Build the image (a `c1541`-formatted 174,848-byte file plus 683 error bytes, one flagged sector per track, and two whole tracks flagged):
|
|
820
|
+
|
|
821
|
+
```text
|
|
822
|
+
SPT = [21]*17 + [19]*7 + [18]*6 + [17]*5 # sectors per track, tracks 1..35
|
|
823
|
+
def index(t, s): return sum(SPT[:t-1]) + s
|
|
824
|
+
img = bytearray(open("disk.d64", "rb").read()) # c1541 -format "TEST,01" d64 disk.d64
|
|
825
|
+
errs = bytearray([0x01] * 683)
|
|
826
|
+
for t, code in {1:0x02, 2:0x03, 3:0x04, 4:0x05, 5:0x07, 6:0x08,
|
|
827
|
+
7:0x09, 8:0x0B, 9:0x0F, 10:0x00, 11:0x01}.items():
|
|
828
|
+
errs[index(t, 0)] = code
|
|
829
|
+
for s in range(21): errs[index(12, s)] = 0x03 # whole track: no sync
|
|
830
|
+
for s in range(21): errs[index(13, s)] = 0x0B # whole track: wrong ID
|
|
831
|
+
open("err2.d64", "wb").write(img + errs) # 175,531 bytes
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
Read each flagged sector through the command channel (BASIC, tokenised with `petcat -w2` from lowercase source):
|
|
835
|
+
|
|
836
|
+
```text
|
|
837
|
+
10 open 15,8,15:open 2,8,2,"#"
|
|
838
|
+
20 for i=1 to 14:read t,s
|
|
839
|
+
30 print#15,"u1 2 0";t;s
|
|
840
|
+
40 input#15,e,e$,a,b
|
|
841
|
+
50 print t;s;e;e$
|
|
842
|
+
60 next:close 2:close 15
|
|
843
|
+
70 data 1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10,0,11,0,8,5,12,0,13,0
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
Run: `x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 -limitcycles 200000000 -drive8truedrive -drive8type 1541 -8 err2.d64 -drive8wobbleamplitude 0 -drive8wobblefrequency 0 -exitscreenshot out.png -autostart read2.prg`. The screen reads, in order: `20`, `20`, `22`, `23`, `0`, `0`, `20`, `20`, `0`, `0`, `0`, then `0` for the control read of 8,5 on the ID-flagged track, `21` for track 12 and `29` for track 13. Three PAL runs gave byte-identical screenshots.
|
|
847
|
+
|
|
848
|
+
### Cross-references
|
|
849
|
+
|
|
850
|
+
- `../formats/c64-file-formats.md`, ".D64": the error block's size, position and the same table
|
|
851
|
+
- `../formats/iec-disk-reference.md`, "The 1541 DOS Error Codes": the ROM's conversion and the full DOS table
|
|
852
|
+
- Technique `disk_protection_tricks`: bad-sector signatures are written as controller codes in a preserved image
|
|
853
|
+
- Pitfall `gcr_timing_assumes_stock_drive`: the other place a loader's model of the drive diverges from the drive
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## tape_bit_is_a_pulse_pair_not_a_pulse — A KERNAL tape bit is a pair of pulses, and there are three pulse lengths, not two
|
|
858
|
+
|
|
859
|
+
**Severity:** high
|
|
860
|
+
**Region:** both
|
|
861
|
+
**Triggered by registers:** DC04, DC05, DC06, DC07, DC0D
|
|
862
|
+
**Triggered by kernal:** LOAD, SAVE
|
|
863
|
+
**Triggered by techniques:** tape_turbo_loader
|
|
864
|
+
|
|
865
|
+
### Symptom
|
|
866
|
+
|
|
867
|
+
A tape tool, a TAP-to-PRG converter or a loader's own tape reader treats each pulse of a KERNAL-written tape as one bit: short means 0, long means 1. It reads garbage. The leader decodes as thousands of zero bits, the filename comes out as noise, no block ever ends where the header says it should, and a checksum written from the same model never matches. A tool built the other way round, one that writes a bit as one pulse, produces a TAP the KERNAL cannot LOAD: it never finds a byte marker and sits at `SEARCHING` until the tape runs out.
|
|
868
|
+
|
|
869
|
+
### Mechanism
|
|
870
|
+
|
|
871
|
+
The KERNAL tape stream has three pulse lengths, and a bit is a pair of them. Measured from a SAVE recorded on the windowless x64sc build of VICE 3.10 (PAL), one TAP entry per full pulse: short pulses centre on `$2F` (376 cycles, about 382 µs), medium on `$43` (536 cycles, about 544 µs) and long on `$58` (704 cycles, about 715 µs), each cluster a few units wide because the write interrupt reprograms the timer from software. Short then medium is a 0, medium then short is a 1, long then medium is the byte marker that opens every byte, and long then short closes a block copy. A byte is twenty pulses: the marker pair, eight data-bit pairs least significant bit first, and a parity pair that makes the count of ones in the nine bits odd. Every block is written twice, each copy opened by a countdown, `$89` down to `$81` before the first and `$09` down to `$01` before the second, and closed by a one-byte XOR checksum of the data and the end-of-block marker.
|
|
872
|
+
|
|
873
|
+
A single-pulse model cannot see any of that. It has no symbol for the medium pulse, so it lumps it with one neighbour or the other, and it has no byte boundary, so any timing hiccup shifts every later bit. The confusion is understandable: the leader before a block really is a run of single short pulses, so the first ten seconds of a tape look like a one-pulse-per-bit stream of zeros, and the medium and long pulses only appear once data starts.
|
|
874
|
+
|
|
875
|
+
### Fix
|
|
876
|
+
|
|
877
|
+
Decode in two stages. First classify every TAP entry into S, M or L by threshold (below `$3A`, `$3A`–`$4B`, above `$4B` worked on the VICE recording; a real cassette needs wider bands). Then walk the symbols in pairs, starting at the first L: `LM` opens a byte, then take nine pairs, `SM` as 0 and `MS` as 1, check that the nine bits hold an odd number of ones, and stop the copy at `LS`. Skip the nine countdown bytes, XOR the rest, and compare with the last byte. Read the second copy the same way when the first copy has a parity or checksum failure. When writing, emit the same pairs, and put a leader of short pulses in front of each copy: the KERNAL wrote 27,137 shorts before the header block and 5,376 before the program block.
|
|
878
|
+
|
|
879
|
+
### Worked example
|
|
880
|
+
|
|
881
|
+
The bad pattern, a Python converter that calls each pulse a bit:
|
|
882
|
+
|
|
883
|
+
```text
|
|
884
|
+
raw = open("save.tap", "rb").read()[20:]
|
|
885
|
+
bits = [0 if b < 0x40 else 1 for b in raw if b] # one pulse, one bit: wrong
|
|
886
|
+
bytes_out = [sum(bits[i+k] << k for k in range(8)) for i in range(0, len(bits) - 8, 8)]
|
|
887
|
+
# prints thousands of $00 for the leader, then noise; no $89 countdown, no filename
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
The fix, on the same file. The first twenty pulses after the leader in the recording were `57 41 43 2E 2F 42 2E 43 42 2F 2F 42 2F 42 30 43 42 2E 2F 41`:
|
|
891
|
+
|
|
892
|
+
```text
|
|
893
|
+
def sym(b):
|
|
894
|
+
return "S" if b < 0x3A else "M" if b < 0x4C else "L"
|
|
895
|
+
|
|
896
|
+
def read_byte(s, i): # s: symbol string, i: index of an 'L'
|
|
897
|
+
if s[i:i+2] != "LM":
|
|
898
|
+
return None # 'LS' is the end of this copy
|
|
899
|
+
bits = []
|
|
900
|
+
for k in range(9):
|
|
901
|
+
pair = s[i+2+2*k : i+4+2*k]
|
|
902
|
+
bits.append({"SM": 0, "MS": 1}[pair])
|
|
903
|
+
assert sum(bits) % 2 == 1 # odd parity over data + parity bit
|
|
904
|
+
return sum(bits[k] << k for k in range(8)), i + 20
|
|
905
|
+
|
|
906
|
+
syms, j = [], 0
|
|
907
|
+
while j < len(raw):
|
|
908
|
+
if raw[j]:
|
|
909
|
+
syms.append(sym(raw[j])); j += 1
|
|
910
|
+
else:
|
|
911
|
+
syms.append("Z"); j += 4 # a zero byte carries a 24-bit count: skip the whole entry
|
|
912
|
+
s = "".join(syms)
|
|
913
|
+
i = s.index("L")
|
|
914
|
+
val, i = read_byte(s, i) # LM MS SM SM MS SM SM SM MS SM -> $89, the first countdown byte
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
Running that over the recording gives `$89 $88 ... $81`, then `$01 $01 $08 $0D $08 $54 $20 ...` for the header (type 1, load `$0801`, end `$080D`, name `T`), and the second copy opens with `$09 $08 ... $01`. The 12-byte program block that follows XORs to `$E6`, the checksum byte the KERNAL wrote after it.
|
|
918
|
+
|
|
919
|
+
### Cross-references
|
|
920
|
+
|
|
921
|
+
- `../formats/c64-file-formats.md`, ".TAP", "KERNAL bit encoding": the measured pulse table, byte layout and block structure
|
|
922
|
+
- `../hardware/cia-reference.md`, the Timer B and `$DC0D` notes: tape write runs on Timer B underflow interrupts and tape read measures pulse widths on the same timer
|
|
923
|
+
- `../hardware/c64-memory-map.md`, `$01`: bit 3 is the write line the pulses come out of, bit 4 the button sense, bit 5 the motor
|
|
924
|
+
- Technique `cpu_io_port_bank`: why a banking write to `$01` must preserve bits 3 to 5 while tape code runs
|
|
925
|
+
- Pitfall `gcr_timing_assumes_stock_drive`: the disk-side pitfall of modelling a medium's timing from the wrong assumption
|
|
926
|
+
|
|
927
|
+
---
|
|
928
|
+
|
|
929
|
+
## exomizer3_proto_flags_mismatch — Exomizer 3 streams need a decruncher built for the same -P bits; a classic decruncher needs -P0 at crunch time
|
|
930
|
+
|
|
931
|
+
**Severity:** high
|
|
932
|
+
**Region:** both
|
|
933
|
+
**Triggered by techniques:** exomizer_basics, crunched_data_in_basic_stub
|
|
934
|
+
|
|
935
|
+
### Symptom
|
|
936
|
+
|
|
937
|
+
A file crunched with Exomizer 3's `mem`, `raw` or `level` command is fed to a decruncher that does not match it: a decruncher written for Exomizer 2, a copy of `exodecrunch.s` lifted from an older production or a tutorial, or the shipped decruncher assembled with a different set of `#define` switches than the `-P` flags used at crunch time. What happens depends on which bits disagree. Measured here on the windowless x64sc build of VICE 3.10: a stream crunched with `-P0` and read by the shipped decruncher at its defaults writes its output straight past the start of the destination, overwrites its own crunched input, and lands in the KERNAL's BRK handler; the machine comes back to a cleared screen and `READY.` with no message. A stream that differs in bit 5 only decrunches to completion and leaves wrong bytes behind, with nothing to say so; which bytes are wrong varies from run to run, because the misread references pull from RAM the image never wrote. The `sfx` command is not affected, because the cruncher embeds a decruncher generated for the same flags.
|
|
938
|
+
|
|
939
|
+
### Mechanism
|
|
940
|
+
|
|
941
|
+
Exomizer 3.0.0 (2018-05-16) changed the crunched bit stream to make the 6502 decruncher faster, and its changelog says the change is incompatible. Which shape the stream takes is set by `-P<bitfield>`, a value from 0 to 63. `exo31info.txt` gives the bits; in our words:
|
|
942
|
+
|
|
943
|
+
| Bit | Value | What it changes in the stream |
|
|
944
|
+
|-----|-------|-------------------------------|
|
|
945
|
+
| 0 | 1 | Bit order inside the stream: set reads most significant bit first, clear is the Exomizer 2 order |
|
|
946
|
+
| 1 | 2 | A read of more than 7 bits is split into a short shift plus a whole byte instead of shifting every bit |
|
|
947
|
+
| 2 | 4 | The first literal byte is implicit, with no flag bit in front of it |
|
|
948
|
+
| 3 | 8 | The stream is aligned toward its start without a shift flag bit |
|
|
949
|
+
| 4 | 16 | Sequences of length 3 get an offset table of their own, three tables instead of two; the decrunch table grows from 156 to 204 bytes |
|
|
950
|
+
| 5 | 32 | Lets a match repeat the offset of the match before it when only one literal byte or one literal run separates them (new in 3.1.0, 2020-12-22) |
|
|
951
|
+
|
|
952
|
+
`raw`, `mem` and `level` defaulted to `-P7` in 3.0 and to `-P39` from 3.1. `-P0` clears every bit and writes the Exomizer 2 format. Since 3.1 the flag also takes relative forms: `-P+16` sets bit 4 and leaves the rest, `-P-32` clears bit 5, and they chain (`-P-32+16`). `sfx` accepts `-P` for bits 2 to 5 and forces bits 0 and 1 on, with a warning; the stub it emits always matches the payload.
|
|
953
|
+
|
|
954
|
+
The stream carries no byte that says which bits it was written with. The decruncher's assumptions are assembled in. In the shipped 6502 decrunchers (`exodecrs/exodecrunch.s` for ca65, and the `kick`, `acme` and `dasm` copies) two of the bits have a switch: `EXTRA_TABLE_ENTRY_FOR_LENGTH_THREE` must be defined for a stream crunched with `-P+16`, and `DONT_REUSE_OFFSET` for one crunched with `-P-32`. Bits 0 to 3 have no switch: those sources read the 3.x form only, so they cannot read a `-P0` stream at all, and a decruncher that reads the `-P0` form cannot read theirs. The readme in `exodecrs/` says the two streaming decrunchers, `exostreamdecr1.s` and `exostreamdecr2.s`, still read the old layout, so streams for them are crunched with `-P0`. The decruncher source also names `DECRUNCH_FORWARDS` for a stream crunched with `-f`, `LITERAL_SEQUENCES_NOT_USED` for `-c` and `MAX_SEQUENCE_LENGTH_256` for `-M256`; the last two are optional size savings, the first is a hard requirement like the `-P` pair.
|
|
955
|
+
|
|
956
|
+
Why the `-P0` case crashes rather than stalling: the decruncher first builds its tables from the encoding at the head of the stream, then copies literals and back-references downward from the end address the stream names. Read with the wrong bit order, the lengths and offsets are noise. In the measured run the output pointer crossed below the destination start with a store to `$2FFF` at 3,100,944 cycles and wrote another 9,747 bytes, one per address, down through the embedded crunched stream (`$0A8B` to `$0C23` in that build) and the decruncher's own 156-byte table (`$09EF` to `$0A8A`) to `$09EC`, before the CPU executed a `$00` byte and reached `$FE66`, the KERNAL's BRK entry, at 3,377,428 cycles. The KERNAL's BRK path goes through the BASIC warm-start vector, which clears the screen and prints `READY.`; the border colour the harness had set was reset along with it, which is the tell that distinguishes this from a hang.
|
|
957
|
+
|
|
958
|
+
### Fix
|
|
959
|
+
|
|
960
|
+
Crunch with the flags the decruncher was built for, and keep the two from the same Exomizer release:
|
|
961
|
+
|
|
962
|
+
- A decruncher from Exomizer 2, a hand-copied one from an older source, or `exostreamdecr1.s`/`exostreamdecr2.s`: add `-P0` to the `mem`, `raw` or `level` command line.
|
|
963
|
+
- The shipped 3.x `exodecrunch` source: use the default flags, or if you add `-P+16` also define `EXTRA_TABLE_ENTRY_FOR_LENGTH_THREE`, and if you add `-P-32` also define `DONT_REUSE_OFFSET`. Match `-f` with `DECRUNCH_FORWARDS`.
|
|
964
|
+
- A third-party loader with Exomizer decrunching built in (Krill's `loadcompd`, for example): read its release notes for the Exomizer version and flags it expects and crunch with those.
|
|
965
|
+
- `sfx` output needs nothing; the stub always matches its payload.
|
|
966
|
+
|
|
967
|
+
When a decrunch misbehaves and the cruncher's version is in doubt, `exomizer -v` prints it, and the stream itself has a crude tell: in our `mem -l auto` files of the same input the third byte was `$01` under the defaults and `$80` under `-P0`. That is an observation from one input, not a documented signature.
|
|
968
|
+
|
|
969
|
+
### Worked example
|
|
970
|
+
|
|
971
|
+
The measurements below were made with Exomizer 3.1.3b0, built here from the author's zlib-licensed source (Bitbucket `magli143/exomizer`, commit `ba91318`, 2025-05-01) with `make` in `src/`, and the KickAssembler decruncher `exodecrs/kick/exodecrunch.asm` from the same tree, assembled with KickAssembler 5.25. The subject is a 4,287-byte PRG that sets the border and background green, writes a marker to `$02FF`, and carries about 4 KB of patterned filler so there is something to crunch.
|
|
972
|
+
|
|
973
|
+
Self-extracting form; this is the tutorial command and it works as written:
|
|
974
|
+
|
|
975
|
+
```text
|
|
976
|
+
exomizer sfx sys -o green_sfx.prg green.prg # 4,287 bytes in, 724 bytes out
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
Run in the windowless x64sc (`-default -warp +sound +autostart-delay-random -autostartprgmode 1 -limitcycles 6000000 -exitscreenshot out.png -autostart green_sfx.prg`), the exit screenshot has a green border and reads `GREEN BORDER OK`; a second run gave a byte-identical PNG.
|
|
980
|
+
|
|
981
|
+
The same file through `mem`, with the defaults and with `-P0`:
|
|
982
|
+
|
|
983
|
+
```text
|
|
984
|
+
exomizer mem -o mem_def.exo green.prg # 412 bytes; identical to -P39
|
|
985
|
+
exomizer mem -P0 -o mem_p0.exo green.prg # 411 bytes
|
|
986
|
+
exomizer mem -P7 -o mem_p7.exo green.prg # 411 bytes (the 3.0 default)
|
|
987
|
+
exomizer mem -P55 -o mem_p55.exo green.prg # 419 bytes (-P+16: the third table)
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
Both `mem_def.exo` and `mem_p0.exo` begin with the load address `$07FE` and end with the decrunched end address `$18BE`; 86 of the 411 bytes between them differ, and the third byte is `$01` in one and `$80` in the other.
|
|
991
|
+
|
|
992
|
+
The decrunch matrix. A harness at `$0801` embeds one stream (crunched with `mem -l none` and the input relocated to `$3000`), calls `exod_decrunch`, then sums the 4,285 output bytes and compares the sum with the expected `$30F1`: border green and `$02FF = $A5` on a match, border red and `$02FF = $E5` on a mismatch, border yellow if it never returns. Each row is one run at 8,000,000 cycles; the first two were repeated and gave byte-identical screenshots.
|
|
993
|
+
|
|
994
|
+
| Decruncher build | Stream | Result |
|
|
995
|
+
|------------------|--------|--------|
|
|
996
|
+
| defaults | `-P39` (default) | green; `30F1 30F1` on screen |
|
|
997
|
+
| defaults | `-P0` | cleared screen, `READY.`, light-blue border: BRK at `$FE66` after 3,377,428 cycles |
|
|
998
|
+
| defaults | `-P7` | the same crash |
|
|
999
|
+
| defaults | `-P55` (`-P+16`) | the same crash |
|
|
1000
|
+
| `EXTRA_TABLE_ENTRY_FOR_LENGTH_THREE` | `-P55` | green; `30F1 30F1` |
|
|
1001
|
+
| `DONT_REUSE_OFFSET` | `-P7` (`-P-32`) | green; `30F1 30F1` |
|
|
1002
|
+
| `DONT_REUSE_OFFSET` | `-P39` | red; finished, wrong bytes, no crash. The wrong sum differs from run to run (three default runs gave `3874`, `3752` and `3612`): the misread back-references pull from RAM outside the decrunched image, which VICE fills with a random component by default. With `-raminitrandomchance 0` added, two runs were byte-identical and read `385F 30F1` |
|
|
1003
|
+
|
|
1004
|
+
The harness's crunched-byte reader is the shipped `main.asm` pattern, a self-modifying `lda $ffff` walked downward from the label after the embedded data. Nothing else in the harness depends on the flags; every difference in the table comes from the `-P` value and the two `#define` lines.
|
|
1005
|
+
|
|
1006
|
+
Not measured here: a genuine Exomizer 2 decruncher against a 3.x stream. The shipped 3.x source has no `-P0` build, and no Exomizer 2 tree was fetched. The `exodecrs/` readme's statement that such a decruncher needs `-P0` is read, not run.
|
|
1007
|
+
|
|
1008
|
+
### Cross-references
|
|
1009
|
+
|
|
1010
|
+
- Technique `exomizer_basics`: the commands, and the version they hold for
|
|
1011
|
+
- Technique `crunched_data_in_basic_stub`: where a hand-rolled or borrowed depacker meets a cruncher's output
|
|
1012
|
+
- Pitfall `krill_cc65_2_18_miscompile`: the other loader-side failure that comes from mismatched tool versions
|
|
1013
|
+
- `exo31info.txt` and `exodecrs/README_exo3.txt` in the Exomizer source tree: the bit definitions and the -P0 note, read for facts only
|
|
1014
|
+
|
|
1015
|
+
---
|
|
1016
|
+
|
|
1017
|
+
## atn_assert_drives_data_low_via_atna — Asserting ATN pulls the drive's DATA line low through the 1541's ATNA gate, whatever the drive program writes
|
|
1018
|
+
|
|
1019
|
+
**Severity:** high
|
|
1020
|
+
**Region:** both
|
|
1021
|
+
**Triggered by registers:** DD00
|
|
1022
|
+
**Triggered by techniques:** drive_code_upload_and_job_queue, krill_loader_integration, sparkle_irq_loader
|
|
1023
|
+
|
|
1024
|
+
### Symptom
|
|
1025
|
+
|
|
1026
|
+
A drive program is uploaded with `M-W`, started with `M-E`, and takes over
|
|
1027
|
+
the bus with a protocol of its own. The host side uses ATN as a strobe or
|
|
1028
|
+
as a clock and reads the drive's reply on DATA IN. The bits sampled under
|
|
1029
|
+
ATN never change; whole bytes come back fixed when the drive's data is
|
|
1030
|
+
constant, and garbled when it is not. Looked at one bit at a time, DATA IN
|
|
1031
|
+
is 0 for as long as ATN is held, whatever the drive program puts in its
|
|
1032
|
+
DATA OUT bit, and follows DATA OUT only while ATN is released; or, if the
|
|
1033
|
+
drive program set its ATNA bit, the other way round (the control rows in
|
|
1034
|
+
the table below show the released-phase reading following DATA OUT). The
|
|
1035
|
+
drive code is correct in isolation and the host code
|
|
1036
|
+
is correct in isolation. The case that prompted this entry was a loader
|
|
1037
|
+
that strobed bit pairs with ATN and read a fixed `$22` for every byte
|
|
1038
|
+
(reported; not measured here).
|
|
1039
|
+
|
|
1040
|
+
### Mechanism
|
|
1041
|
+
|
|
1042
|
+
The 1541 does not connect its DATA OUT bit straight to the bus. The DATA
|
|
1043
|
+
line's driver is fed by DATA OUT or-ed with a second term, ATN IN
|
|
1044
|
+
exclusive-or ATNA, where ATNA is bit 4 of `$1800`, the "attention
|
|
1045
|
+
acknowledge" output of the serial VIA. Whenever the level of the ATN line
|
|
1046
|
+
differs from the ATNA bit, that term is 1 and the hardware pulls DATA low.
|
|
1047
|
+
This is how a 1541 answers ATN before any code has run: ATN falls, ATNA is
|
|
1048
|
+
still 0, DATA drops at once. The DOS's ATN service then sets ATNA to 1,
|
|
1049
|
+
which takes the hardware pull off and hands DATA back to the DATA OUT bit,
|
|
1050
|
+
and the idle loop clears ATNA again once ATN is released (see the ROM's
|
|
1051
|
+
`$EBE7` entry in `../formats/iec-disk-reference.md`, "1541 Drive ROM"). The
|
|
1052
|
+
gate itself is on the 1541 schematic (rung 4 here); its effect is measured
|
|
1053
|
+
below.
|
|
1054
|
+
|
|
1055
|
+
A resident drive program that leaves ATNA at 0 therefore gets the same
|
|
1056
|
+
hardware answer every time the host asserts ATN: DATA goes low, and
|
|
1057
|
+
nothing the program writes to DATA OUT can lift it, because the term is
|
|
1058
|
+
or-ed in. Set ATNA to 1 and the mirror image happens: DATA is held low the
|
|
1059
|
+
whole time ATN is released, and freed only while ATN is asserted. Either
|
|
1060
|
+
way a host that strobes ATN and reads DATA sees ATN, not data.
|
|
1061
|
+
|
|
1062
|
+
Measured in VICE x64sc 3.10, headless build, PAL and NTSC, with true drive
|
|
1063
|
+
emulation of a 1541 and a `TEST,01` image attached: a drive program of
|
|
1064
|
+
12 or 14 bytes (34 for the last row, and that one also went up in a
|
|
1065
|
+
single `M-W`) uploaded to `$0500` and started with `M-E` did
|
|
1066
|
+
`SEI`, cleared DATA OUT, CLK OUT and ATNA in `$1800`, set the bits the row
|
|
1067
|
+
names, and looped for ever. The host then released its own ATN, CLK OUT and
|
|
1068
|
+
DATA OUT (`$DD00` bits 3 to 5 clear), waited about 25,000 cycles, read
|
|
1069
|
+
`$DD00` eight times some 2,600 cycles apart, asserted ATN (bit 3 set) and
|
|
1070
|
+
did the same, then released it and did the same again. All eight reads in
|
|
1071
|
+
every phase of every run were the one byte the table gives. The drive's
|
|
1072
|
+
`$1800` is the monitor's read (`m 8:1800`) at the start of each phase.
|
|
1073
|
+
On the host, `$DD00` bit 7 is DATA IN and 1 means the line is released;
|
|
1074
|
+
on the drive, `$1800` bit 0 is DATA IN and 1 means the line is low, bit 7
|
|
1075
|
+
is ATN IN and 1 means ATN is asserted. The two control rows fix the sense:
|
|
1076
|
+
with DATA OUT held low by the drive program, the host read bit 7 clear
|
|
1077
|
+
in every phase.
|
|
1078
|
+
|
|
1079
|
+
| Drive program holds | `$DD00`, ATN released | `$DD00`, ATN asserted | `$DD00`, released again | drive `$1800`, same three phases |
|
|
1080
|
+
|---|---|---|---|---|
|
|
1081
|
+
| ATNA 0, DATA OUT 0 | `$C7` (DATA released) | `$4F` (DATA low) | `$C7` | `00`, `81`, `00` |
|
|
1082
|
+
| ATNA 1, DATA OUT 0 | `$47` (DATA low) | `$CF` (DATA released) | `$47` | `11`, `90`, `11` |
|
|
1083
|
+
| ATNA 0, DATA OUT 1 (control) | `$47` | `$4F` | `$47` | `03`, `83`, `03` |
|
|
1084
|
+
| ATNA 1, DATA OUT 1 (control) | `$47` | `$4F` | `$47` | `13`, `93`, `13` |
|
|
1085
|
+
| interrupts left enabled, ATNA 0, DATA OUT 0 | `$C7` | `$4F` | `$C7` | `00`, `81`, `00` |
|
|
1086
|
+
| ATNA copied from ATN IN on every pass (the fix) | `$C7` | `$CF` | `$C7` | `00`, `90`, `00` |
|
|
1087
|
+
|
|
1088
|
+
PAL and NTSC gave the same bytes in the two rows run on both (the first
|
|
1089
|
+
two); the other rows are PAL. In the first row the drive never touched
|
|
1090
|
+
DATA OUT (its `$1800` bit 1 read 0 throughout) and DATA still went low
|
|
1091
|
+
with ATN; in the second it went low without ATN. Leaving the drive's
|
|
1092
|
+
interrupts enabled changes nothing: the ATN edge raised the drive's IRQ,
|
|
1093
|
+
which set the DOS's attention flag at `$7C` from `00` to `01` (measured,
|
|
1094
|
+
monitor read at each phase; it stayed `01` after ATN was released), but
|
|
1095
|
+
the routine that would act on it, set ATNA and take the bus runs from the
|
|
1096
|
+
DOS idle loop (ROM listing, rung 4), and a resident program never returns
|
|
1097
|
+
there. So the DOS does not "take over" a bus a resident program holds; it
|
|
1098
|
+
only notes that it was asked to.
|
|
1099
|
+
|
|
1100
|
+
### Fix
|
|
1101
|
+
|
|
1102
|
+
Either keep ATN out of the data phase, or make the drive program track it.
|
|
1103
|
+
|
|
1104
|
+
1. Do not use ATN as a data-phase signal. Run the custom protocol on CLK
|
|
1105
|
+
and DATA with ATN released on the host side, as the KERNAL itself does
|
|
1106
|
+
between the command bytes. Reserve ATN for what the DOS expects it to
|
|
1107
|
+
mean: get the drive's attention. This is the simple case and the one
|
|
1108
|
+
the job-queue recipe leaves the bus in.
|
|
1109
|
+
2. If ATN is the strobe, the drive program must copy ATN IN into ATNA
|
|
1110
|
+
every time it changes, before it drives or reads DATA. With ATNA equal
|
|
1111
|
+
to the ATN level the exclusive-or term is 0 and DATA belongs to DATA
|
|
1112
|
+
OUT again. The last table row is that fix running: DATA read released
|
|
1113
|
+
in all three phases. Loaders that clock bit pairs with ATN do this
|
|
1114
|
+
inside their drive-side receive loop; a program that copies their host
|
|
1115
|
+
half and writes its own drive half without the ATNA update meets this
|
|
1116
|
+
pitfall on the first byte.
|
|
1117
|
+
|
|
1118
|
+
Both halves matter on the host too: the C64's `$DD00` has no such gate,
|
|
1119
|
+
so the host cannot see the problem from its own port; it just reads what
|
|
1120
|
+
the drive's hardware put on the line.
|
|
1121
|
+
|
|
1122
|
+
### Worked example
|
|
1123
|
+
|
|
1124
|
+
Host side, the read that shows the fault. With a drive program that has
|
|
1125
|
+
cleared ATNA and released DATA, the first sample is `$C7` and the second
|
|
1126
|
+
`$4F`: DATA followed ATN. With ATNA set they are `$47` and `$CF`.
|
|
1127
|
+
|
|
1128
|
+
```kick
|
|
1129
|
+
// Sample DATA IN with ATN released, then with ATN asserted.
|
|
1130
|
+
// $DD00 bit 7 is DATA IN (1 = released); bit 3 is ATN OUT (1 = pull low).
|
|
1131
|
+
lda $dd00
|
|
1132
|
+
and #$07 // keep VIC bank and TXD; ATN, CLK OUT, DATA OUT released
|
|
1133
|
+
sta $dd00
|
|
1134
|
+
jsr settle // a few thousand cycles
|
|
1135
|
+
lda $dd00
|
|
1136
|
+
sta samples // measured: $C7 with ATNA clear, $47 with ATNA set
|
|
1137
|
+
lda $dd00
|
|
1138
|
+
and #$07
|
|
1139
|
+
ora #$08 // assert ATN
|
|
1140
|
+
sta $dd00
|
|
1141
|
+
jsr settle
|
|
1142
|
+
lda $dd00
|
|
1143
|
+
sta samples+1 // measured: $4F with ATNA clear, $CF with ATNA set
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
The drive program that produced those bytes, assembled for `$0500` and
|
|
1147
|
+
started with `M-E` (the second `ora` selects the row):
|
|
1148
|
+
|
|
1149
|
+
```text
|
|
1150
|
+
sei
|
|
1151
|
+
lda $1800
|
|
1152
|
+
and #$e5 ; DATA OUT (bit 1), CLK OUT (bit 3), ATNA (bit 4) all clear
|
|
1153
|
+
ora #$00 ; $10 for the ATNA-set row; $02 to hold DATA OUT low as a control
|
|
1154
|
+
sta $1800
|
|
1155
|
+
loop jmp loop
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
The fix, in the same loop: move ATN IN down to the ATNA position and write
|
|
1159
|
+
it back each pass, so the two never differ for longer than the loop takes.
|
|
1160
|
+
|
|
1161
|
+
```text
|
|
1162
|
+
loop lda $1800
|
|
1163
|
+
lsr
|
|
1164
|
+
lsr
|
|
1165
|
+
lsr
|
|
1166
|
+
and #$10 ; ATN IN (bit 7) in the ATNA position (bit 4)
|
|
1167
|
+
sta $05f0
|
|
1168
|
+
lda $1800
|
|
1169
|
+
and #$ef
|
|
1170
|
+
ora $05f0
|
|
1171
|
+
sta $1800
|
|
1172
|
+
jmp loop
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
That loop is 22 bytes on top of the 12-byte program, and the whole 34
|
|
1176
|
+
bytes went to the drive in one `M-W` (the technique's 32-byte figure is
|
|
1177
|
+
what loaders send, not a limit the emulated DOS enforced here; 35 or more
|
|
1178
|
+
was not tried).
|
|
1179
|
+
|
|
1180
|
+
Not measured here: the same on a 1571 or 1581 (different ports and
|
|
1181
|
+
addresses), an SD2IEC (no code upload at all), and how long the hardware pull lasts after ATN
|
|
1182
|
+
changes when a real drive's loop is slower than the host's strobe.
|
|
1183
|
+
|
|
1184
|
+
### Cross-references
|
|
1185
|
+
|
|
1186
|
+
- Technique `drive_code_upload_and_job_queue`: how the program gets onto the drive and what `M-E` does with it
|
|
1187
|
+
- Techniques `krill_loader_integration` and `sparkle_irq_loader`: protocols that clock bit pairs with ATN, and so must keep ATNA in step
|
|
1188
|
+
- Recipe `../recipes/kickassembler/drive-job-queue.md`: the upload, readback and execute code the measurement reused
|
|
1189
|
+
- `../formats/iec-disk-reference.md`, "Pin Map and Electrical Characteristics": the sense of the host's bits, which the control rows here agree with; "1541 VIA registers, measured": `$1800` bit by bit. Its ATN IN row reads `1` in every state, and until this entry it said ATN was released in all of them; here bit 7 read `0` with ATN released and `1` with it asserted in every run, both models, so the row now says `1` is the asserted level. Those rows were `M-R` reads, and the DOS runs a command while the UNLISTEN that ends it is still under ATN (ROM flow, rung 4, an inference not measured here), which is why the table saw `1`
|
|
1190
|
+
- Register `$DD00` (CIA2), `../hardware/cia-reference.md`: bits 3 to 5 out, 6 and 7 in
|
|
1191
|
+
- Pitfall `fastloader_dd00_write_corrupts_resident`: the other way a `$DD00` bit ends up meaning something the loader did not intend
|