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,1615 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: kernal
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- doc-type: pitfall-reference -->
|
|
6
|
+
|
|
7
|
+
# KERNAL and I/O Pitfalls
|
|
8
|
+
|
|
9
|
+
Most pitfalls in this document share a common thread: they arise from
|
|
10
|
+
the KERNAL's implicit contract with the surrounding hardware state (three
|
|
11
|
+
entries sit outside that thread — the Oscar64 `krnio_save()` splat, the c1541
|
|
12
|
+
uppercase-filename PETSCII shift and the Oscar64 `getchx()` RETURN remap are
|
|
13
|
+
library and host-tooling traps in the same disk-and-keyboard I/O workflow — and
|
|
14
|
+
the last entry is a hardware-wiring trap: RESTORE drives /NMI directly, so no
|
|
15
|
+
CIA mask reaches it). The KERNAL
|
|
16
|
+
was written assuming a specific execution environment — interrupts enabled,
|
|
17
|
+
registers free to clobber, the CPU memory map at its stock $37 configuration,
|
|
18
|
+
and decimal mode cleared. Each entry below describes one way that assumption
|
|
19
|
+
collides with the real-world context of a demo or game that has customised
|
|
20
|
+
IRQs, banked memory, or BCD arithmetic.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## kernal_assumes_sei_cleared — KERNAL re-enables interrupts internally; calling under SEI breaks IRQ discipline
|
|
25
|
+
|
|
26
|
+
**Severity:** high
|
|
27
|
+
**Region:** both
|
|
28
|
+
**Triggered by kernal:** OPEN, LOAD, SAVE, CHKIN, CHKOUT, CLOSE, CLRCHN
|
|
29
|
+
**Triggered by techniques:** stable_raster_irq, kernal_file_write_seq, kernal_file_read_seq, error_channel_check, kernal_load_to_address, kernal_relative_file_io, directory_read_and_select
|
|
30
|
+
|
|
31
|
+
### Symptom
|
|
32
|
+
|
|
33
|
+
A raster IRQ scheme runs cleanly until the code issues a disk or tape I/O call.
|
|
34
|
+
Immediately after the JSR to OPEN, LOAD or CHKIN, the raster split collapses:
|
|
35
|
+
color bars bleed, sprites misplace, or the entire display tears. In more subtle
|
|
36
|
+
cases the raster handler appears to execute twice on the same frame, or the
|
|
37
|
+
stable-raster double-IRQ polling loop hangs indefinitely. With tape routines the
|
|
38
|
+
symptom is often a complete machine hang — the KERNAL is waiting for a
|
|
39
|
+
keyboard character inside CLI context, the raster IRQ fires at an unexpected
|
|
40
|
+
stack depth, and the machine can no longer process RESTORE.
|
|
41
|
+
|
|
42
|
+
A variation: the code wraps a KERNAL call in SEI to protect a critical section.
|
|
43
|
+
The routine returns normally but the critical section's register state has been
|
|
44
|
+
scrambled — the KERNAL's own internal CLI re-enabled IRQs and the raster handler
|
|
45
|
+
fired mid-section.
|
|
46
|
+
|
|
47
|
+
### Mechanism
|
|
48
|
+
|
|
49
|
+
The CLIs are not in OPEN, LOAD, SAVE, CHKIN, CHKOUT, CLOSE or CLRCHN
|
|
50
|
+
themselves — none of those bodies contains a CLI (ROM census of
|
|
51
|
+
`kernal-901227-03.bin`: no $58 byte in OPEN $F34A-$F3D4, CHKIN $F20E-$F24F,
|
|
52
|
+
CHKOUT $F250-$F290, LOAD $F49E-$F5DC or SAVE $F5DD-$F68E; an earlier version
|
|
53
|
+
of this entry said the CLIs were "in their bodies") — but in the serial-bus
|
|
54
|
+
primitives they call (LISTEN, TALK, SECOND, TKSA, ACPTR, CIOUT, UNTALK,
|
|
55
|
+
UNLSN, $ED09-$EEB2). Each primitive brackets its bit-level handshake in
|
|
56
|
+
SEI ... CLI: the byte transfer itself runs with interrupts DISABLED, and the
|
|
57
|
+
routine exits with an unconditional CLI ($EDAB and $EDB5 after LISTEN/TALK/
|
|
58
|
+
SECOND, $EDDB after the bus turnaround, $EE82 after ACPTR) whatever the I flag
|
|
59
|
+
the caller had. Every routine that reaches them on a serial device — OPEN,
|
|
60
|
+
CHKIN, CHKOUT, CLOSE, CLRCHN, LOAD and SAVE — therefore returns with
|
|
61
|
+
interrupts enabled (measured in VICE x64sc with a true-drive 1541 and traps
|
|
62
|
+
off: all seven return with I=0 when entered under SEI). Tape does the same by
|
|
63
|
+
a different route: tape LOAD and SAVE set up their own IRQ under SEI and then
|
|
64
|
+
execute an unconditional CLI at $F8BD, run the whole transfer with interrupts
|
|
65
|
+
enabled, and the restore at $FC93 (PHP/SEI ... PLP) puts back that post-CLI
|
|
66
|
+
state, so they too return with I=0.
|
|
67
|
+
|
|
68
|
+
This is by design: OPEN, LOAD, and SAVE can take millions of cycles (a
|
|
69
|
+
standard KERNAL IEC LOAD from a 1541 runs at roughly 300-600 bytes per
|
|
70
|
+
second, i.e. two to three seconds per kilobyte — measured in VICE x64sc with
|
|
71
|
+
true drive emulation: 8,192 bytes in 871 jiffies, about 14.5 s;
|
|
72
|
+
`hardware/cia-reference.md`, `formats/iec-disk-reference.md` and
|
|
73
|
+
`techniques/loaders-packers.md` measure the same order. An earlier version of
|
|
74
|
+
this entry said "tens of thousands of cycles" and "roughly 1 second per
|
|
75
|
+
kilobyte", which understated the exposure window by half), and the jiffy
|
|
76
|
+
clock IRQ at $EA31 must continue running during that time to keep the
|
|
77
|
+
60/50 Hz time base accurate and to service the keyboard queue. The KERNAL
|
|
78
|
+
authors assumed the caller had IRQs enabled at the time of the JSR — the machine
|
|
79
|
+
boots with CLI, BASIC runs with CLI, and the KERNAL's own IRQ handler at $EA31
|
|
80
|
+
is designed to be re-entrant only in specific ways.
|
|
81
|
+
|
|
82
|
+
When a demo or game installs a custom raster IRQ scheme, the raster handler's
|
|
83
|
+
stack discipline assumes IRQs arrive only when the CPU is in the main loop. If
|
|
84
|
+
the main loop calls OPEN under SEI and the KERNAL executes CLI, the raster IRQ
|
|
85
|
+
fires with the stack at an unexpected depth. The handler RTIs into the middle of
|
|
86
|
+
the KERNAL routine, and the KERNAL either hangs or corrupts its own zero-page
|
|
87
|
+
workspace because the handler saved new state on top of the KERNAL's
|
|
88
|
+
partially-built stack frame.
|
|
89
|
+
|
|
90
|
+
SETLFS and SETNAM do not contain CLI (they merely store values in zero page —
|
|
91
|
+
SETLFS at $FE00 is STA $B8/STX $BA/STY $B9/RTS, and P measured after
|
|
92
|
+
SEI;SETLFS is $35, I still set), but OPEN, LOAD, SAVE, CHKIN, CHKOUT, CLRCHN
|
|
93
|
+
and CLOSE all return with interrupts enabled when the channel is a serial-bus
|
|
94
|
+
(IEC) device — the CLI sits in the byte-send tail at $EDAB and the receive
|
|
95
|
+
tail at $EE82, so LISTEN/TALK/SECOND/TKSA/ACPTR/UNLSN/UNTLK inherit it.
|
|
96
|
+
Addressed to the screen or keyboard (devices 0-3) the same calls leave the I
|
|
97
|
+
flag untouched (measured: CHKIN on a screen file under SEI left P = $36).
|
|
98
|
+
An earlier version of this entry listed SETLFS on the trigger line and left
|
|
99
|
+
CLOSE, CLRCHN and SAVE off it.
|
|
100
|
+
|
|
101
|
+
### Fix
|
|
102
|
+
|
|
103
|
+
Never call KERNAL file I/O routines from inside a raster IRQ handler. The IRQ
|
|
104
|
+
handler is the wrong context for long, unpredictable-duration operations.
|
|
105
|
+
|
|
106
|
+
Leave IRQs enabled (CLI) when issuing KERNAL file I/O. The raster IRQ will
|
|
107
|
+
fire during the operation, but as long as the handler is reentrant-safe this
|
|
108
|
+
is harmless. If a critical section must use SEI, complete it before the KERNAL
|
|
109
|
+
call:
|
|
110
|
+
|
|
111
|
+
```kick
|
|
112
|
+
sei // Begin critical section
|
|
113
|
+
// ... modify shared state ...
|
|
114
|
+
cli // End critical section BEFORE KERNAL call
|
|
115
|
+
jsr $ffba // SETLFS — safe; IRQs are now enabled
|
|
116
|
+
jsr $ffbd // SETNAM
|
|
117
|
+
jsr $ffc0 // OPEN — may internally CLI/SEI; we are already at CLI
|
|
118
|
+
bcs open_error
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If I/O must be triggered from a protected context, set a flag and service the
|
|
122
|
+
KERNAL call from the main loop's next idle frame, where IRQs are enabled and
|
|
123
|
+
the stack is at its natural depth.
|
|
124
|
+
|
|
125
|
+
### Worked example
|
|
126
|
+
|
|
127
|
+
```kick
|
|
128
|
+
// BAD: KERNAL OPEN called from inside the raster IRQ handler.
|
|
129
|
+
// OPEN's serial primitives execute CLI; the raster scheme re-enters at wrong stack depth.
|
|
130
|
+
raster_irq_bad:
|
|
131
|
+
lda #WHITE
|
|
132
|
+
sta $d020
|
|
133
|
+
jsr $ffc0 // OPEN from IRQ — WRONG; CLI inside OPEN corrupts IRQ scheme
|
|
134
|
+
asl $d019
|
|
135
|
+
rti
|
|
136
|
+
|
|
137
|
+
// GOOD: queue a flag; main loop services I/O with IRQs naturally enabled.
|
|
138
|
+
io_requested: .byte 0 // flag byte (lives with the code, not in zero page)
|
|
139
|
+
|
|
140
|
+
raster_irq_good:
|
|
141
|
+
lda #WHITE
|
|
142
|
+
sta $d020
|
|
143
|
+
asl $d019
|
|
144
|
+
rti
|
|
145
|
+
|
|
146
|
+
main_loop:
|
|
147
|
+
lda io_requested
|
|
148
|
+
beq main_loop
|
|
149
|
+
lda #0
|
|
150
|
+
sta io_requested
|
|
151
|
+
// IRQs are enabled here — KERNAL calls are safe
|
|
152
|
+
lda #1
|
|
153
|
+
ldx #8
|
|
154
|
+
ldy #2
|
|
155
|
+
jsr $ffba // SETLFS
|
|
156
|
+
lda #fname_end - fname
|
|
157
|
+
ldx #<fname
|
|
158
|
+
ldy #>fname
|
|
159
|
+
jsr $ffbd // SETNAM
|
|
160
|
+
jsr $ffc0 // OPEN — KERNAL may CLI/SEI internally; fine here
|
|
161
|
+
bcs io_error
|
|
162
|
+
jmp main_loop
|
|
163
|
+
|
|
164
|
+
fname: .text "MYFILE,S,R"
|
|
165
|
+
fname_end:
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Cross-references
|
|
169
|
+
|
|
170
|
+
- Technique: `stable_raster_irq` — the raster scheme most likely to be
|
|
171
|
+
disrupted by an unexpected CLI from the KERNAL; the technique doc covers
|
|
172
|
+
IRQ vector placement ($0314 through the $FF48 dispatcher versus
|
|
173
|
+
$FFFE/$FFFF with the KERNAL out). Stack depth across a chained pair of
|
|
174
|
+
handlers is covered by `double_irq` (the dispatcher's own TSX clobbers X,
|
|
175
|
+
so a saved stack pointer goes through memory) and in detail by
|
|
176
|
+
`recipes/kickassembler/stable-raster-irq.md`, section "The stack".
|
|
177
|
+
- KERNAL routines: `SETLFS` ($FFBA), `OPEN` ($FFC0), `LOAD` ($FFD5),
|
|
178
|
+
`SAVE` ($FFD8), `CHKIN` ($FFC6), `CHKOUT` ($FFC9), `CLOSE` ($FFC3),
|
|
179
|
+
`CLRCHN` ($FFCC).
|
|
180
|
+
- Pitfall: `kernal_io_mapping_dependency` — the two pitfalls often appear
|
|
181
|
+
together; banking out the KERNAL while also calling OPEN is doubly fatal.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## kernal_clobbers_a_x_y — KERNAL routines do not preserve A, X, or Y unless the Affects line says otherwise
|
|
186
|
+
|
|
187
|
+
**Severity:** medium
|
|
188
|
+
**Region:** both
|
|
189
|
+
**Triggered by kernal:** CHROUT, CHRIN, GETIN, CHKIN, CHKOUT
|
|
190
|
+
**Triggered by techniques:** text_input_line, kernal_relative_file_io, petscii_screen_code_conversion
|
|
191
|
+
|
|
192
|
+
### Symptom
|
|
193
|
+
|
|
194
|
+
A loop that calls GETIN to poll the keyboard loses its Y index. A sprite
|
|
195
|
+
multiplex routine that calls GETIN to check for keypresses returns with the
|
|
196
|
+
sprite index in Y replaced by the key code and X by the old queue length — but
|
|
197
|
+
only when a key was waiting; with an empty queue both come back intact, so the
|
|
198
|
+
bug appears only while the player types (measured in VICE x64sc: empty queue,
|
|
199
|
+
X=$77/Y=$88 unchanged; one key queued, X=$01, Y=$41 — an earlier version of
|
|
200
|
+
this entry said Y came back as zero, which it never does for a real key). A
|
|
201
|
+
character output sequence that builds a string index in X finds X reset to a
|
|
202
|
+
garbage value after CHKOUT. The bugs are particularly elusive because they
|
|
203
|
+
surface only on certain code paths — if the developer tests the loop without
|
|
204
|
+
any KERNAL calls inserted, everything is fine; adding a single JSR $FFE4 breaks
|
|
205
|
+
the loop silently if the developer assumed Y was preserved. (An earlier version
|
|
206
|
+
of this entry used CHROUT as the example; CHROUT preserves A on success, so
|
|
207
|
+
that loop worked as written — see the contract list below.)
|
|
208
|
+
|
|
209
|
+
A subtler variant: the code saves only A around CHKIN, assumes X and Y are
|
|
210
|
+
untouched, then finds X changed on return — on a disk or other serial-bus
|
|
211
|
+
channel it is the device number (8), on a keyboard or screen file it is the
|
|
212
|
+
open-file-table index (0 for the first file), on a tape file it is the stored
|
|
213
|
+
secondary address ($60) — and mistakes it for an error sentinel. An earlier
|
|
214
|
+
version of this entry said X was always the table index; ROM $F237 is TAX on
|
|
215
|
+
the device number before TALK, and CHKIN on logical file 1, device 8 was
|
|
216
|
+
measured in VICE x64sc returning X=$08.
|
|
217
|
+
|
|
218
|
+
### Mechanism
|
|
219
|
+
|
|
220
|
+
The KERNAL authors saved space and cycles by preserving only the registers that
|
|
221
|
+
callers genuinely need back. The documented contract is the Affects line in each
|
|
222
|
+
routine's reference entry. Any register listed in Affects may be changed; any
|
|
223
|
+
register absent from Affects is not guaranteed preserved either — treat absence
|
|
224
|
+
as "not documented as changed on the success path only." The safe assumption is:
|
|
225
|
+
any register not explicitly listed as output is potentially clobbered.
|
|
226
|
+
|
|
227
|
+
Specific contracts for the most-called routines:
|
|
228
|
+
|
|
229
|
+
- **CHROUT ($FFD2):** Affects C; A is preserved on success (C=0) and comes
|
|
230
|
+
back as 0 on the error return (C=1) — KERNAL reference; measured in VICE
|
|
231
|
+
x64sc (LDA #$41 / JSR $FFD2 to the screen returned A=$41) and in ROM ($E716
|
|
232
|
+
PHA … $E6B0 PLA/TAX/PLA/CLC/CLI/RTS; error tail $F201 LDA $9E / BCC +2 /
|
|
233
|
+
LDA #0 / RTS). X and Y are preserved. An earlier version of this entry listed A
|
|
234
|
+
as clobbered.
|
|
235
|
+
- **CHRIN ($FFCF):** Affects A, X, Y, C — all three registers may change.
|
|
236
|
+
- **GETIN ($FFE4):** Affects A, X, Y, C — same as CHRIN.
|
|
237
|
+
- **CHKIN ($FFC6):** Affects A, X, C. A returns the device number on success
|
|
238
|
+
(A=8 disk, A=3 screen, A=0 keyboard, measured; ROM $F233 STA $99 with
|
|
239
|
+
A = FA).
|
|
240
|
+
- **CHKOUT ($FFC9):** Affects A, X, C.
|
|
241
|
+
|
|
242
|
+
OPEN, CLOSE, LOAD, and SAVE clobber A, X, Y, and C. Only SETLFS and SETNAM
|
|
243
|
+
preserve all three (their Affects lines say None).
|
|
244
|
+
|
|
245
|
+
### Fix
|
|
246
|
+
|
|
247
|
+
Audit every KERNAL call site. For each JSR $FFxx, check the Affects line. Save
|
|
248
|
+
and restore every register listed in Affects that the caller also needs after
|
|
249
|
+
the call returns. Use PHA/PLA for A, TXA/PHA/PLA/TAX for X, and TYA/PHA/PLA/TAY
|
|
250
|
+
for Y.
|
|
251
|
+
|
|
252
|
+
Unless you have verified the Affects line and confirmed which registers the
|
|
253
|
+
caller doesn't need after the call, preserve all three around every KERNAL JSR.
|
|
254
|
+
The cost is 29 cycles (13 to save, 16 to restore, from the 6510 reference's
|
|
255
|
+
per-instruction figures: PHA 3, PLA 4, the transfers 2 each) plus the
|
|
256
|
+
JSR/RTS — negligible outside of tight raster loops. An earlier version of this
|
|
257
|
+
entry said 15, which no subset that saves all three registers can reach.
|
|
258
|
+
|
|
259
|
+
```kick
|
|
260
|
+
// Canonical full-preservation wrapper macro.
|
|
261
|
+
// Saves A, X, Y; calls the given KERNAL address; restores Y, X, A.
|
|
262
|
+
.macro KernalCall(addr) {
|
|
263
|
+
pha
|
|
264
|
+
txa
|
|
265
|
+
pha
|
|
266
|
+
tya
|
|
267
|
+
pha
|
|
268
|
+
jsr addr
|
|
269
|
+
pla
|
|
270
|
+
tay
|
|
271
|
+
pla
|
|
272
|
+
tax
|
|
273
|
+
pla
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
In tight loops, check Affects and save only what is needed. If the counter is
|
|
278
|
+
in X and the routine touches only A and C (e.g. CHROUT), a bare PHA/JSR/PLA
|
|
279
|
+
suffices — and for CHROUT even that is only needed if you take the error path.
|
|
280
|
+
|
|
281
|
+
### Worked example
|
|
282
|
+
|
|
283
|
+
```kick
|
|
284
|
+
// BAD: assumes CHKIN leaves X untouched.
|
|
285
|
+
// CHKIN Affects A and X; on a disk channel X comes back as the device number
|
|
286
|
+
// (8 here), not 1; on a screen or keyboard file it is the open-file-table
|
|
287
|
+
// index (measured in VICE x64sc).
|
|
288
|
+
ldx #1 // logical file number
|
|
289
|
+
jsr $ffc6 // CHKIN — X is clobbered
|
|
290
|
+
cpx #1 // WRONG: comparing clobbered X
|
|
291
|
+
|
|
292
|
+
// GOOD: save and restore around CHKIN.
|
|
293
|
+
ldx #1
|
|
294
|
+
txa
|
|
295
|
+
pha // save X
|
|
296
|
+
jsr $ffc6 // CHKIN — Affects: A, X, C
|
|
297
|
+
bcs chkin_error
|
|
298
|
+
pla
|
|
299
|
+
tax // restore X = 1
|
|
300
|
+
|
|
301
|
+
// BAD: keyboard poll loop keeps its count in Y across GETIN (Y is in Affects).
|
|
302
|
+
// With the queue empty Y survives, so the loop works until the player types;
|
|
303
|
+
// then GETIN hands back the key code in Y and the count is gone.
|
|
304
|
+
poll_loop:
|
|
305
|
+
jsr $ffe4 // GETIN — A, X, Y may change
|
|
306
|
+
dey // WRONG: Y may now be the key code
|
|
307
|
+
bne poll_loop
|
|
308
|
+
|
|
309
|
+
// GOOD: save Y around GETIN.
|
|
310
|
+
poll_loop_good:
|
|
311
|
+
tya
|
|
312
|
+
pha
|
|
313
|
+
jsr $ffe4 // GETIN — Affects: A, X, Y, C
|
|
314
|
+
pla
|
|
315
|
+
tay
|
|
316
|
+
dey
|
|
317
|
+
bne poll_loop_good
|
|
318
|
+
|
|
319
|
+
// CHROUT is the exception this entry used to get wrong: A comes back intact
|
|
320
|
+
// when C=0 and as 0 when C=1, so test C after CHROUT, not A.
|
|
321
|
+
print_loop:
|
|
322
|
+
lda msg,y
|
|
323
|
+
jsr $ffd2 // CHROUT — A preserved on success, C=1 on error
|
|
324
|
+
bcs print_error
|
|
325
|
+
iny
|
|
326
|
+
dex
|
|
327
|
+
bne print_loop
|
|
328
|
+
print_error:
|
|
329
|
+
|
|
330
|
+
msg: .text "HELLO"
|
|
331
|
+
msg_end:
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Cross-references
|
|
335
|
+
|
|
336
|
+
- KERNAL routines: `CHROUT` ($FFD2), `CHRIN` ($FFCF), `GETIN` ($FFE4),
|
|
337
|
+
`CHKIN` ($FFC6), `CHKOUT` ($FFC9).
|
|
338
|
+
- Pitfall: `kernal_assumes_sei_cleared` — both pitfalls stem from the same
|
|
339
|
+
root cause: the KERNAL assumes a specific runtime contract that the caller
|
|
340
|
+
must uphold.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## decimal_mode_in_irq_handler — IRQ may fire mid-SED arithmetic; handler must CLD on entry or BCD corruption ensues
|
|
345
|
+
|
|
346
|
+
**Severity:** critical
|
|
347
|
+
**Region:** both
|
|
348
|
+
**Triggered by techniques:** stable_raster_irq, decimal_mode_pitfalls, irq_chain_table, logic_rate_decoupling, sprite_multiplex_game, tod_alarm_interrupt, irq_keyboard_own_scan
|
|
349
|
+
|
|
350
|
+
### Symptom
|
|
351
|
+
|
|
352
|
+
BCD arithmetic running in the main loop produces correct results in isolation
|
|
353
|
+
but yields random garbage when the raster IRQ fires during the calculation.
|
|
354
|
+
The corruption is intermittent — it depends on whether the IRQ fires between
|
|
355
|
+
the SED and CLD instructions in the main-loop BCD block. Debugging is painful
|
|
356
|
+
because inserting a SEI/CLI wrapper around the BCD block "fixes" the problem
|
|
357
|
+
(it does — by preventing the IRQ from firing during the critical window) without
|
|
358
|
+
revealing the real cause. The same corruption appears in SID player timing
|
|
359
|
+
code that uses BCD to accumulate tick counts, and in any BASIC-style score
|
|
360
|
+
counter kept as packed BCD in zero page.
|
|
361
|
+
|
|
362
|
+
A related variant: two handlers coexist (music player + raster effect) and one
|
|
363
|
+
uses SED/CLD for a BCD calculation. If the second handler fires while the first
|
|
364
|
+
is between SED and CLD — via NMI or a chained IRQ that re-enables interrupts —
|
|
365
|
+
the second handler's ADC/SBC produces wrong results.
|
|
366
|
+
|
|
367
|
+
### Mechanism
|
|
368
|
+
|
|
369
|
+
The 6502/6510 decimal mode flag (D flag) is one bit of the processor status
|
|
370
|
+
register (P). When D = 1, ADC and SBC perform BCD (binary-coded-decimal)
|
|
371
|
+
arithmetic: result nibbles are adjusted so that each hex digit represents a
|
|
372
|
+
decimal digit 0-9. When D = 0, addition and subtraction are pure binary.
|
|
373
|
+
|
|
374
|
+
Crucially, the D flag is **not automatically cleared or saved on IRQ entry**.
|
|
375
|
+
The 6502 interrupt sequence pushes the program counter (PCH, PCL) and the
|
|
376
|
+
processor status register (P) onto the stack, then fetches the IRQ vector and
|
|
377
|
+
begins executing the handler. The pushed P contains the D flag as it was at
|
|
378
|
+
the time of the interrupt — but the processor itself does not clear D. The
|
|
379
|
+
handler runs with D still set if the interrupted code had executed SED and not
|
|
380
|
+
yet executed CLD.
|
|
381
|
+
|
|
382
|
+
This means: if main-loop code is between `SED` and `CLD`, and an IRQ fires,
|
|
383
|
+
the IRQ handler's ADC and SBC instructions run in BCD mode. Any addition or
|
|
384
|
+
subtraction in the handler — including pointer arithmetic, counter decrements,
|
|
385
|
+
and index calculations — will produce BCD-adjusted results instead of binary
|
|
386
|
+
results. A handler that adds 8 to a pointer stored in zero page will compute
|
|
387
|
+
8 + 0 = 8 correctly in BCD, but 9 + 1 will produce $10 (decimal 10 as BCD)
|
|
388
|
+
instead of $0A (decimal 10 as binary). The pointer lands in the wrong page.
|
|
389
|
+
The raster effect writes to the wrong address. The corruption is data-dependent
|
|
390
|
+
and nearly impossible to trace without knowing that D is set.
|
|
391
|
+
|
|
392
|
+
On NMOS silicon, N, V, and Z are undefined after a decimal-mode ADC/SBC. C
|
|
393
|
+
reflects decimal carry. Code in the handler that branches on carry after an
|
|
394
|
+
addition will take the wrong branch when D is unexpectedly set.
|
|
395
|
+
|
|
396
|
+
RTI restores P from the stack, including D. After RTI, the main-loop BCD block
|
|
397
|
+
resumes with D = 1 as expected — but the damage inside the handler's execution
|
|
398
|
+
window may already have corrupted effect or music state for the current frame.
|
|
399
|
+
|
|
400
|
+
### Fix
|
|
401
|
+
|
|
402
|
+
Every IRQ handler must execute `CLD` as part of its entry stanza, before any
|
|
403
|
+
arithmetic. The canonical IRQ handler prelude is:
|
|
404
|
+
|
|
405
|
+
```kick
|
|
406
|
+
irq_handler:
|
|
407
|
+
pha // save A
|
|
408
|
+
txa
|
|
409
|
+
pha // save X
|
|
410
|
+
tya
|
|
411
|
+
pha // save Y
|
|
412
|
+
cld // !! MANDATORY: clear decimal mode !!
|
|
413
|
+
// ... handler body — all arithmetic is now binary ...
|
|
414
|
+
pla
|
|
415
|
+
tay
|
|
416
|
+
pla
|
|
417
|
+
tax
|
|
418
|
+
pla
|
|
419
|
+
rti // RTI restores P, which restores the D flag
|
|
420
|
+
// to whatever the interrupted code had set
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
The `CLD` costs 2 cycles and 1 byte. It is not optional. Place it after the
|
|
424
|
+
register saves (the PHA/TXA/PHA/TYA/PHA sequence does not perform arithmetic,
|
|
425
|
+
so those instructions are safe even with D set) and before any instruction that
|
|
426
|
+
uses ADC, SBC, or reads the C/N/V/Z flags after such an instruction.
|
|
427
|
+
|
|
428
|
+
The RTI at the end of the handler automatically restores D (along with all
|
|
429
|
+
other flags) from the pushed P — so the main-loop BCD block resumes correctly
|
|
430
|
+
with D = 1 after the handler exits. No explicit SED is needed in the handler's
|
|
431
|
+
exit path.
|
|
432
|
+
|
|
433
|
+
NMI handlers require the same discipline. The NMI vector fires regardless of
|
|
434
|
+
the I flag and also does not clear D — if you use the NMI for digi playback or
|
|
435
|
+
any other purpose, it needs CLD too.
|
|
436
|
+
|
|
437
|
+
### Worked example
|
|
438
|
+
|
|
439
|
+
```kick
|
|
440
|
+
// BAD: handler has no CLD. ADC in the handler runs in BCD mode if main-loop
|
|
441
|
+
// code was between SED and CLD when the IRQ fired.
|
|
442
|
+
// E.g. $15 + $06 = $1B binary, but BCD adjusts it to $21 — wrong sprite Y.
|
|
443
|
+
irq_bad:
|
|
444
|
+
pha
|
|
445
|
+
txa
|
|
446
|
+
pha
|
|
447
|
+
tya
|
|
448
|
+
pha
|
|
449
|
+
// No CLD!
|
|
450
|
+
lda sprite_y,x
|
|
451
|
+
clc
|
|
452
|
+
adc #21 // BCD mode if D=1: result may be wrong
|
|
453
|
+
sta $d001
|
|
454
|
+
pla
|
|
455
|
+
tay
|
|
456
|
+
pla
|
|
457
|
+
tax
|
|
458
|
+
pla
|
|
459
|
+
rti
|
|
460
|
+
|
|
461
|
+
// GOOD: CLD as the first post-save instruction.
|
|
462
|
+
irq_good:
|
|
463
|
+
pha
|
|
464
|
+
txa
|
|
465
|
+
pha
|
|
466
|
+
tya
|
|
467
|
+
pha
|
|
468
|
+
cld // Mandatory: clears D before any arithmetic
|
|
469
|
+
lda sprite_y,x
|
|
470
|
+
clc
|
|
471
|
+
adc #21 // Always binary — correct regardless of D on entry
|
|
472
|
+
sta $d001
|
|
473
|
+
pla
|
|
474
|
+
tay
|
|
475
|
+
pla
|
|
476
|
+
tax
|
|
477
|
+
pla
|
|
478
|
+
rti // RTI restores P from stack, including original D flag
|
|
479
|
+
|
|
480
|
+
// Main-loop BCD block — safe with the corrected handler above.
|
|
481
|
+
update_score:
|
|
482
|
+
sed
|
|
483
|
+
lda score_lo
|
|
484
|
+
clc
|
|
485
|
+
adc #$10 // +10 in BCD
|
|
486
|
+
sta score_lo
|
|
487
|
+
lda score_hi
|
|
488
|
+
adc #$00
|
|
489
|
+
sta score_hi
|
|
490
|
+
cld // Exit BCD mode — keep this window as short as possible
|
|
491
|
+
rts
|
|
492
|
+
|
|
493
|
+
score_lo: .byte 0
|
|
494
|
+
score_hi: .byte 0
|
|
495
|
+
sprite_y: .fill 8, i * 21 + 50
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Cross-references
|
|
499
|
+
|
|
500
|
+
- Technique: `stable_raster_irq` — the raster handler this pitfall most
|
|
501
|
+
commonly hits. The technique doc does not show a handler prelude (it only
|
|
502
|
+
notes that the KERNAL dispatcher pushes A, X and Y); the
|
|
503
|
+
PHA/TXA/PHA/TYA/PHA + CLD stanza above is the reference form. Neither
|
|
504
|
+
stable-raster recipe currently executes CLD, and on the Oscar64 side
|
|
505
|
+
`rasterirq.h`'s own ISRs do not either — the KERNAL's $FF48 dispatcher and
|
|
506
|
+
$EA31 service routine contain no CLD, so a handler reached through $0314
|
|
507
|
+
inherits whatever D was. (An earlier version of this entry said the
|
|
508
|
+
technique doc discussed the prelude; it does not.)
|
|
509
|
+
- Opcodes: `SED` ($F8), `CLD` ($D8) — the two instructions that set and clear
|
|
510
|
+
the decimal flag.
|
|
511
|
+
- Pitfall: `kernal_clobbers_a_x_y` — the PHA/TXA/PHA/TYA/PHA prelude described
|
|
512
|
+
in both pitfalls is the same stanza; the CLD belongs between the last PHA
|
|
513
|
+
and the first handler instruction.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## kernal_io_mapping_dependency — KERNAL file/disk routines crash when $01 has banked out the KERNAL ROM
|
|
518
|
+
|
|
519
|
+
**Severity:** high
|
|
520
|
+
**Region:** both
|
|
521
|
+
**Triggered by kernal:** SETLFS, LOAD, SAVE, OPEN, CLOSE
|
|
522
|
+
**Triggered by techniques:** cpu_io_port_bank, kernal_file_write_seq, kernal_file_read_seq, error_channel_check, kernal_load_to_address, kernal_relative_file_io, directory_read_and_select
|
|
523
|
+
|
|
524
|
+
### Symptom
|
|
525
|
+
|
|
526
|
+
Disk or tape I/O that works in a plain BASIC or kernal environment crashes
|
|
527
|
+
silently when called from a demo or game that has changed $01 to enable an
|
|
528
|
+
all-RAM or partial-RAM banking mode. The crash is typically a wild branch: the
|
|
529
|
+
CPU fetches an opcode from RAM at $E000-$FFFF where the KERNAL ROM used to be,
|
|
530
|
+
executes whatever byte it finds there as an opcode, and veers off into garbage.
|
|
531
|
+
In some configurations the machine appears to freeze; in others it resets.
|
|
532
|
+
|
|
533
|
+
A variant: the code correctly sets $01 = $37 for the KERNAL call, but later
|
|
534
|
+
restores $01 to $34 (all RAM) or $35 (I/O + KERNAL but no BASIC) without
|
|
535
|
+
saving and restoring the exact value it found. Subsequent KERNAL calls use the
|
|
536
|
+
restored value, which may not match what the KERNAL's internal routines expect.
|
|
537
|
+
|
|
538
|
+
A related issue: code banks in $35 for bitmap RAM access, then calls CHROUT.
|
|
539
|
+
CHROUT at $FFD2 is a three-byte JMP in KERNAL ROM. With HIRAM = 0 ($35 has
|
|
540
|
+
bit 1 clear), the address $FFD2 contains RAM, not ROM. The JSR executes
|
|
541
|
+
whatever bytes happen to be there and branches to a garbage address.
|
|
542
|
+
|
|
543
|
+
### Mechanism
|
|
544
|
+
|
|
545
|
+
The 6510 processor port at $01 controls three memory mapping lines through the
|
|
546
|
+
PLA (906114):
|
|
547
|
+
|
|
548
|
+
- **Bit 0 (LORAM):** When 0, $A000-$BFFF is RAM. When 1 (and HIRAM = 1),
|
|
549
|
+
$A000-$BFFF is BASIC ROM.
|
|
550
|
+
- **Bit 1 (HIRAM):** When 0, $E000-$FFFF is RAM. When 1, $E000-$FFFF is
|
|
551
|
+
KERNAL ROM.
|
|
552
|
+
- **Bit 2 (CHAREN):** When 0, $D000-$DFFF is character ROM (if HIRAM or LORAM).
|
|
553
|
+
When 1, $D000-$DFFF is the I/O block (VIC-II, SID, CIA1, CIA2).
|
|
554
|
+
|
|
555
|
+
The stock reset state is $01 = $37 (binary %00110111), giving LORAM=1, HIRAM=1,
|
|
556
|
+
CHAREN=1 — KERNAL ROM at $E000-$FFFF, BASIC ROM at $A000-$BFFF, I/O at
|
|
557
|
+
$D000-$DFFF.
|
|
558
|
+
|
|
559
|
+
Common demo/game banking modes and their effects on KERNAL accessibility:
|
|
560
|
+
|
|
561
|
+
| $01 value | LORAM | HIRAM | CHAREN | KERNAL ROM visible? | I/O visible? |
|
|
562
|
+
|-----------|-------|-------|--------|---------------------|--------------|
|
|
563
|
+
| $37 | 1 | 1 | 1 | **Yes** | Yes |
|
|
564
|
+
| $36 | 0 | 1 | 1 | **Yes** | Yes (BASIC hidden) |
|
|
565
|
+
| $35 | 1 | 0 | 1 | **No — RAM** | Yes |
|
|
566
|
+
| $34 | 0 | 0 | 1 | **No — RAM** | Yes |
|
|
567
|
+
| $33 | 1 | 1 | 0 | **Yes** | No (char ROM)|
|
|
568
|
+
| $30 | 0 | 0 | 0 | **No — RAM** | No |
|
|
569
|
+
|
|
570
|
+
Modes $35, $34, and $30 are popular in demos because they allow the CPU to
|
|
571
|
+
see RAM at $E000-$FFFF (useful for placing time-critical code, decompression
|
|
572
|
+
buffers, or sprite data up high). But KERNAL ROM must be visible ($01 bit 1
|
|
573
|
+
HIRAM = 1) for any KERNAL call — including the jump-table entries themselves —
|
|
574
|
+
to work. The jump table entries at $FF81-$FFF3 are three-byte JMP instructions
|
|
575
|
+
in KERNAL ROM. If HIRAM = 0, those addresses contain whatever the program wrote
|
|
576
|
+
into RAM there, not JMP instructions.
|
|
577
|
+
|
|
578
|
+
KERNAL routines also require I/O to be visible (CHAREN = 1) because they drive
|
|
579
|
+
CIA1/CIA2 and the IEC bus directly. The safe values for KERNAL file I/O are
|
|
580
|
+
$37 (stock) or $36 (no BASIC, which is fine since the KERNAL does not call
|
|
581
|
+
BASIC ROM during I/O). Both have HIRAM = 1 and CHAREN = 1.
|
|
582
|
+
|
|
583
|
+
### Fix
|
|
584
|
+
|
|
585
|
+
Wrap every KERNAL file I/O call with a save-and-restore of $01:
|
|
586
|
+
|
|
587
|
+
```kick
|
|
588
|
+
.macro KernalBankIn() {
|
|
589
|
+
lda $01
|
|
590
|
+
pha // save current banking state
|
|
591
|
+
lda #$37
|
|
592
|
+
sta $01 // KERNAL + I/O + BASIC all visible
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.macro KernalBankOut() {
|
|
596
|
+
pla
|
|
597
|
+
sta $01 // restore caller's banking state
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
This ensures:
|
|
602
|
+
1. The KERNAL jump table entries are ROM (HIRAM = 1).
|
|
603
|
+
2. The CIA1/CIA2 I/O is visible (CHAREN = 1).
|
|
604
|
+
3. The exact caller's $01 value is restored after the call, not a hardcoded
|
|
605
|
+
value that may not match what the caller had.
|
|
606
|
+
|
|
607
|
+
The save is to the stack (PHA/PLA). Using a zero-page byte for the save is
|
|
608
|
+
also common, but the stack is simpler and does not require allocating a
|
|
609
|
+
zero-page scratch byte.
|
|
610
|
+
|
|
611
|
+
### Worked example
|
|
612
|
+
|
|
613
|
+
```kick
|
|
614
|
+
// BAD: LOAD called with $01 = $35 (HIRAM = 0 — KERNAL ROM is RAM).
|
|
615
|
+
// JSR $FFD5 executes whatever byte is in RAM at $FFD5, not the KERNAL JMP.
|
|
616
|
+
lda #$35
|
|
617
|
+
sta $01 // Bank out KERNAL for bitmap buffer access
|
|
618
|
+
lda #0
|
|
619
|
+
ldx #<$0801
|
|
620
|
+
ldy #>$0801
|
|
621
|
+
jsr $ffd5 // CRASHES — $FFD5 is RAM, not KERNAL jump table
|
|
622
|
+
|
|
623
|
+
// GOOD: save $01, bank in KERNAL, call, restore.
|
|
624
|
+
lda $01
|
|
625
|
+
pha // Save current banking state ($35)
|
|
626
|
+
lda #$37
|
|
627
|
+
sta $01 // KERNAL ROM + I/O visible
|
|
628
|
+
lda #0
|
|
629
|
+
ldx #<$0801
|
|
630
|
+
ldy #>$0801
|
|
631
|
+
jsr $ffd5 // LOAD — safe: $FFD5 is KERNAL ROM JMP
|
|
632
|
+
pla
|
|
633
|
+
sta $01 // Restore $35 — bitmap RAM back at $E000
|
|
634
|
+
|
|
635
|
+
// BAD: restoring to a hard-coded literal rather than the saved value.
|
|
636
|
+
lda $01
|
|
637
|
+
pha
|
|
638
|
+
lda #$37
|
|
639
|
+
sta $01
|
|
640
|
+
jsr $ffc0 // OPEN
|
|
641
|
+
lda #$35
|
|
642
|
+
sta $01 // !! Wrong if caller had $36 or $34
|
|
643
|
+
|
|
644
|
+
// CORRECT: always PLA back, never restore to a literal.
|
|
645
|
+
lda $01
|
|
646
|
+
pha
|
|
647
|
+
lda #$37
|
|
648
|
+
sta $01
|
|
649
|
+
jsr $ffc0 // OPEN
|
|
650
|
+
pla
|
|
651
|
+
sta $01 // Exact pre-call value, whatever it was
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Cross-references
|
|
655
|
+
|
|
656
|
+
- Memory region [$0000-$0001 — Processor I/O port](../hardware/c64-memory-map.md#0000-0001--processor-io-port)
|
|
657
|
+
— the 6510 processor port data register; bits 0-2 control
|
|
658
|
+
LORAM/HIRAM/CHAREN. Resolvable via `c64_memory_map 0001`, not
|
|
659
|
+
`c64_register_lookup` (the KB has no Register node for the CPU port).
|
|
660
|
+
- KERNAL routines: `SETLFS` ($FFBA), `OPEN` ($FFC0), `CLOSE` ($FFC3),
|
|
661
|
+
`LOAD` ($FFD5), `SAVE` ($FFD8).
|
|
662
|
+
- Pitfall: `kernal_assumes_sei_cleared` — the two pitfalls are frequently
|
|
663
|
+
encountered together in demo loaders; OPEN can simultaneously trip both
|
|
664
|
+
if the caller has disabled IRQs and banked out the KERNAL.
|
|
665
|
+
- `docs/hardware/c64-memory-map.md` — full PLA truth table with all 32 banking
|
|
666
|
+
mode combinations.
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## krnio_save_leaves_splat_file — a *PRG splat after krnio_save() means the emulator was stopped before the drive finished, not that SAVE is broken
|
|
671
|
+
|
|
672
|
+
**Severity:** high
|
|
673
|
+
**Region:** both
|
|
674
|
+
**Triggered by kernal:** SAVE, OPEN, CLOSE
|
|
675
|
+
|
|
676
|
+
### Symptom
|
|
677
|
+
|
|
678
|
+
An Oscar64 game calls `krnio_save(8, &state, &state + sizeof(state))` to
|
|
679
|
+
persist a struct to a `.d64` attached as drive 8. The function returns
|
|
680
|
+
`true` (apparent success) and a directory entry appears with the chosen
|
|
681
|
+
filename — but the entry is marked `*PRG` with 0 blocks, and the file
|
|
682
|
+
cannot be read back via `c1541 -read`, via the game's own `krnio_load`,
|
|
683
|
+
or via BASIC `LOAD"NAME",8`. It appears in two forms: no blocks allocated
|
|
684
|
+
at all; or blocks allocated (the free-block count drops) with no directory
|
|
685
|
+
record of them. Both forms were reproduced on demand by terminating x64sc
|
|
686
|
+
with `-limitcycles` while the 1541 was still writing — which is what the
|
|
687
|
+
symptom means (see Mechanism).
|
|
688
|
+
|
|
689
|
+
The `*` flag preceding `PRG` in a `c1541 -list` output is the
|
|
690
|
+
"improperly closed" marker: the directory entry was created, but the
|
|
691
|
+
DOS-side close handshake that finalises block count and clears the
|
|
692
|
+
splat bit never completed.
|
|
693
|
+
|
|
694
|
+
### Mechanism
|
|
695
|
+
|
|
696
|
+
Oscar64's `krnio_save()` wraps KERNAL `SAVE` ($FFD8) — SETLFS plus
|
|
697
|
+
`JSR $FFD8`, per `kernalio.c` — which is the same routine that BASIC's
|
|
698
|
+
`SAVE"NAME",8` invokes. The KERNAL SAVE flow snapshots a contiguous memory
|
|
699
|
+
block as a PRG file (two-byte load address header + raw bytes): it forces
|
|
700
|
+
the secondary address to $61 at $F5FA (ROM bytes A9 61 85 B9) and closes the
|
|
701
|
+
file through the KERNAL's own IEC close path. It is not broken. Under VICE
|
|
702
|
+
3.10 with true drive emulation — the default, and the only configuration
|
|
703
|
+
in which drive 8 exists under `-default`; with `+drive8truedrive` there is
|
|
704
|
+
no device 8 at all and the program never returns from its first IEC call —
|
|
705
|
+
`krnio_save()` of a 64-byte struct to a fresh c1541-formatted `.d64`
|
|
706
|
+
produces a clean 1-block PRG (`c1541 -list`: `1 "tideline" prg`, no `*`,
|
|
707
|
+
662 blocks free) that reads back intact (66 bytes = load address + struct),
|
|
708
|
+
and `krnio_save` returns true. Built with Oscar64 2026-05-19 and run in
|
|
709
|
+
x64sc 3.10; an earlier version of this entry said the splat was
|
|
710
|
+
"observed under VICE 3.x with and without -drive8truedrive", and that
|
|
711
|
+
does not reproduce.
|
|
712
|
+
|
|
713
|
+
The splat arises when the emulator exits (`-limitcycles`,
|
|
714
|
+
`-exitscreenshot`, window close) or the program resets while the 1541 is
|
|
715
|
+
still writing: the directory entry has been created but the DOS-side
|
|
716
|
+
close that finalises the block count has not happened yet. A 64-byte SAVE
|
|
717
|
+
needs on the order of 1-2 M emulated cycles after autostart with true drive
|
|
718
|
+
on. The open/write/close pattern splats identically when cut at the same
|
|
719
|
+
point (measured: `*prg`, 643 blocks free), so it is not a remedy for this
|
|
720
|
+
symptom.
|
|
721
|
+
|
|
722
|
+
The Oscar64 sample at `samples/kernalio/filewrite.c` does NOT use
|
|
723
|
+
`krnio_save`. It uses the structured file API instead:
|
|
724
|
+
|
|
725
|
+
1. `krnio_setnam("@0:NAME,P,W")` — name with the `@0:` replace prefix
|
|
726
|
+
and a `,P,W` (PRG write) suffix
|
|
727
|
+
2. `krnio_open(fnum, dev, 2)` — open a logical file on a secondary
|
|
728
|
+
address that the DOS recognises as a user data channel (2)
|
|
729
|
+
3. `krnio_write(fnum, data, size)` — stream bytes through the channel
|
|
730
|
+
4. `krnio_close(fnum)` — close, which gives the DOS the explicit
|
|
731
|
+
"end of file" signal that finalises the directory entry
|
|
732
|
+
|
|
733
|
+
This sequence does not rely on the KERNAL SAVE routine at all; it produces
|
|
734
|
+
a file with no 2-byte load-address header, reads back symmetrically with
|
|
735
|
+
`krnio_read`, and takes the `@0:` replace prefix.
|
|
736
|
+
|
|
737
|
+
### Fix
|
|
738
|
+
|
|
739
|
+
Give the drive time to finish. In a headless run, do not cut x64sc before
|
|
740
|
+
the program's own done marker appears on screen; interactively, wait for
|
|
741
|
+
the busy LED to go out or for `krnio_save` to return — and check the disk
|
|
742
|
+
only afterwards. An earlier version of this entry told you to avoid
|
|
743
|
+
`krnio_save()`; the splat it described was the emulator being stopped
|
|
744
|
+
mid-write, and the same cut splats the open/write/close pattern too.
|
|
745
|
+
|
|
746
|
+
`krnio_open` / `krnio_write` / `krnio_close` is still the better fit for
|
|
747
|
+
arbitrary structured data — game state, score tables, scenario maps — on
|
|
748
|
+
its real merits: no 2-byte load-address header in the file, a symmetric
|
|
749
|
+
read with `krnio_read`, and `@0:` replace semantics. Reserve `krnio_save()`
|
|
750
|
+
for genuinely BASIC-compatible memory snapshots (e.g. a sprite table at a
|
|
751
|
+
fixed address that BASIC will `LOAD` into the right place).
|
|
752
|
+
|
|
753
|
+
### Worked example
|
|
754
|
+
|
|
755
|
+
```c
|
|
756
|
+
// krnio_save produces a clean PRG (load address + bytes) when the drive is
|
|
757
|
+
// allowed to finish; measured under VICE 3.10 with true drive emulation.
|
|
758
|
+
krnio_setnam("TIDELINE,P,W");
|
|
759
|
+
bool ok = krnio_save(8,
|
|
760
|
+
(const char*)&state,
|
|
761
|
+
(const char*)&state + sizeof(state));
|
|
762
|
+
// ok == true; c1541 -list shows 1 "tideline" prg, 1 block, reads back as
|
|
763
|
+
// 66 bytes ($0B00 load address + the 64-byte struct).
|
|
764
|
+
|
|
765
|
+
// PREFERRED for structured data: open/write/close as in
|
|
766
|
+
// samples/kernalio/filewrite.c (no load-address header, symmetric read).
|
|
767
|
+
krnio_setnam("@0:TIDELINE,P,W");
|
|
768
|
+
if (krnio_open(2, 8, 2)) {
|
|
769
|
+
krnio_write(2, (const char*)&state, sizeof(state));
|
|
770
|
+
krnio_close(2);
|
|
771
|
+
}
|
|
772
|
+
// Read back symmetrically: setnam("TIDELINE,P,R"); open(2, 8, 2);
|
|
773
|
+
// read(2, &state, sizeof(state)); close(2).
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
The `@0:` replace prefix is important if the file might already exist.
|
|
777
|
+
Without it the drive refuses the open with DOS error 63 FILE EXISTS — but
|
|
778
|
+
`krnio_open()` still returns true, because it only reports KERNAL OPEN's
|
|
779
|
+
carry, which a serial device sets for device-not-present or too-many-files,
|
|
780
|
+
never for a DOS error. The following `krnio_write()` also reports the full
|
|
781
|
+
byte count, yet nothing reaches the disk and the old file is left as it was
|
|
782
|
+
(measured under VICE 3.10: open returned true, write returned 64, command
|
|
783
|
+
channel read 63, file contents unchanged; an earlier version of this entry
|
|
784
|
+
said the call returned false). If you need to know, open the command channel
|
|
785
|
+
(secondary address 15) and `krnio_read` the status line after the open.
|
|
786
|
+
|
|
787
|
+
### Cross-references
|
|
788
|
+
|
|
789
|
+
- Oscar64 sample: `samples/kernalio/filewrite.c` and `fileread.c` —
|
|
790
|
+
the authoritative pattern.
|
|
791
|
+
- Oscar64 header: `c64/kernalio.h` exposes both `krnio_save` (memory
|
|
792
|
+
snapshots with a load address) and `krnio_open`/`write`/`close`
|
|
793
|
+
(structured data).
|
|
794
|
+
- KERNAL routines: `SETNAM` ($FFBD), `SETLFS` ($FFBA), `OPEN`
|
|
795
|
+
($FFC0), `CHKOUT` ($FFC9), `CHROUT` ($FFD2), `CLRCHN` ($FFCC),
|
|
796
|
+
`CLOSE` ($FFC3). `krnio_open` chains setlfs + open; `krnio_write`
|
|
797
|
+
chains chkout + per-byte chrout + clrchn.
|
|
798
|
+
- Pitfall: `kernal_io_mapping_dependency` — both save APIs need the
|
|
799
|
+
KERNAL ROM banked in at $E000 when called.
|
|
800
|
+
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
## c1541_uppercase_filename_petscii_shift — c1541 -write of an UPPERCASE c64 name stores shifted PETSCII the game can't open
|
|
804
|
+
|
|
805
|
+
**Severity:** medium
|
|
806
|
+
**Region:** both
|
|
807
|
+
**Triggered by kernal:** SETNAM, OPEN
|
|
808
|
+
**Triggered by techniques:** kernal_file_write_seq, kernal_file_read_seq, kernal_load_to_address, directory_read_and_select
|
|
809
|
+
|
|
810
|
+
### Symptom
|
|
811
|
+
|
|
812
|
+
A host tool authors a data file onto a `.d64` with
|
|
813
|
+
`c1541 -attach disk.d64 -write host.bin TDLVL00`. The file appears in the
|
|
814
|
+
directory, and `c1541 -read TDLVL00` round-trips it byte-for-byte. But the C64
|
|
815
|
+
program that opens it with `krnio_setnam("TDLVL00")` + `krnio_open(2, 8, 2)`
|
|
816
|
+
gets an open that appears to succeed — `krnio_open` returns true, because
|
|
817
|
+
KERNAL OPEN's carry reports only device-not-present and table errors, never a
|
|
818
|
+
DOS error — but `krnio_read` returns 0 bytes and the command channel (open
|
|
819
|
+
15,8,15 and read) answers `62, FILE NOT FOUND` (measured in VICE x64sc; an
|
|
820
|
+
earlier version of this entry said `krnio_open` returned false). Listing the
|
|
821
|
+
disk directory from inside the emulator (`LOAD"$",8` then `LIST`) shows the
|
|
822
|
+
filename rendered as graphics characters (e.g. `#####00`) instead of `TDLVL00`.
|
|
823
|
+
A file the *game itself* wrote with the same logical name (via `krnio_write`)
|
|
824
|
+
opens fine — only the host-authored file fails.
|
|
825
|
+
|
|
826
|
+
### Mechanism
|
|
827
|
+
|
|
828
|
+
`c1541` converts the host-supplied c64 filename from ASCII to PETSCII:
|
|
829
|
+
|
|
830
|
+
- **lowercase** ASCII `a-z` (0x61-0x7A) → unshifted PETSCII 0x41-0x5A, which
|
|
831
|
+
render as `A-Z` in the default uppercase/graphics charset.
|
|
832
|
+
- **UPPERCASE** ASCII `A-Z` (0x41-0x5A) → **shifted** PETSCII 0xC1-0xDA, which
|
|
833
|
+
render as graphics symbols in uppercase mode.
|
|
834
|
+
|
|
835
|
+
So `-write host.bin TDLVL00` stores the filename bytes
|
|
836
|
+
`0xD4,0xC4,0xCC,0xD6,0xCC,0x30,0x30`.
|
|
837
|
+
|
|
838
|
+
A C64 program (e.g. Oscar64) sends the bytes of its C-string literal to SETNAM.
|
|
839
|
+
The char literals `'T','D','L','V','L'` are ASCII / PETSCII-uppercase
|
|
840
|
+
`0x54,0x44,0x4C,0x56,0x4C`. The 1541 DOS matches directory filenames
|
|
841
|
+
**byte-for-byte**, so `0x54...` never matches the stored `0xD4...`, and the
|
|
842
|
+
drive answers 62, FILE NOT FOUND on the command channel; KERNAL OPEN itself
|
|
843
|
+
still returns C=0, so the C64 side sees a successful open with an empty file.
|
|
844
|
+
|
|
845
|
+
Game-written files are immune because the game both writes and reads with the
|
|
846
|
+
same ASCII byte sequence — they match each other regardless of absolute
|
|
847
|
+
encoding. The mismatch appears only when one side is `c1541` (host) and the
|
|
848
|
+
other is the game (C64). `c1541 -read` is symmetric with `-write`, so a
|
|
849
|
+
host-only round-trip never reveals the problem — only a real C64-side open does.
|
|
850
|
+
|
|
851
|
+
### Fix
|
|
852
|
+
|
|
853
|
+
Pass the c64 filename to `c1541` in **lowercase**. `c1541` then maps lowercase
|
|
854
|
+
ASCII `a-z` to unshifted PETSCII 0x41-0x5A — i.e. `'t'` (0x74) → 0x54 — exactly
|
|
855
|
+
the bytes the game's uppercase-ASCII C string requests, and they render
|
|
856
|
+
correctly as `TDLVL00` in the C64 directory.
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
# BAD: uppercase c64 name -> shifted PETSCII; game krnio_open("TDLVL00") returns true but reads 0 bytes (DOS error 62).
|
|
860
|
+
c1541 -attach disk.d64 -write tdlvl00.bin TDLVL00
|
|
861
|
+
|
|
862
|
+
# GOOD: lowercase c64 name -> 0x54... matches the game's request + renders right.
|
|
863
|
+
c1541 -attach disk.d64 -write tdlvl00.bin tdlvl00
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Diagnose a suspected mismatch by listing the directory from inside the running
|
|
867
|
+
emulator (`LOAD"$",8` then `LIST`): a name rendered as graphics characters is
|
|
868
|
+
the tell. Test for the condition by reading the error channel after OPEN, or
|
|
869
|
+
by checking `krnio_read`'s byte count — not by testing `krnio_open`'s return
|
|
870
|
+
value, which is true either way. (The autostart program name's encoding does
|
|
871
|
+
not matter — autostart loads by directory position, not by name match.)
|
|
872
|
+
|
|
873
|
+
### Worked example
|
|
874
|
+
|
|
875
|
+
```python
|
|
876
|
+
# Host authoring tool (Python + c1541). The on-disk file content is identical
|
|
877
|
+
# either way; only the FILENAME's PETSCII encoding differs.
|
|
878
|
+
slot = 0
|
|
879
|
+
c64name = f"tdlvl{slot:02d}" # lowercase -> matches game's krnio_setnam("TDLVL00")
|
|
880
|
+
# c64name = f"TDLVL{slot:02d}" # WRONG: uppercase -> shifted PETSCII -> no match
|
|
881
|
+
subprocess.run(["c1541", "-attach", "disk.d64", "-write", "tdlvl00.bin", c64name])
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
### Cross-references
|
|
885
|
+
|
|
886
|
+
- KERNAL routines: `SETNAM` ($FFBD), `OPEN` ($FFC0). The 1541 DOS matches
|
|
887
|
+
directory filenames byte-for-byte; SETNAM passes the bytes verbatim.
|
|
888
|
+
- Pitfall: `krnio_save_leaves_splat_file` — the other half of the host↔C64
|
|
889
|
+
disk-data round-trip; both live in the structured-file save/load workflow,
|
|
890
|
+
and both are cases where `krnio_open`/`krnio_save` return true and the
|
|
891
|
+
evidence is on the disk or the command channel, not in the return value.
|
|
892
|
+
- Tooling: VICE `c1541 -write` / `-read` perform ASCII→PETSCII on the c64
|
|
893
|
+
filename; their conversion is self-consistent, which is why a host-only
|
|
894
|
+
round-trip hides the bug.
|
|
895
|
+
- Discovered authoring Tideline level files (`TDLVLnn`) for Phase H4; see
|
|
896
|
+
`loop/games/CLAUDE.md` disk-testing notes.
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
## getchx_petscii_remaps_return — Oscar64 getchx() delivers RETURN as $0A (not $0D) under every charmap except IOCHM_TRANSPARENT — including the default IOCHM_ASCII
|
|
901
|
+
|
|
902
|
+
**Severity:** medium
|
|
903
|
+
**Region:** both
|
|
904
|
+
**Triggered by kernal:** GETIN
|
|
905
|
+
**Triggered by techniques:** text_input_line
|
|
906
|
+
|
|
907
|
+
### Symptom
|
|
908
|
+
|
|
909
|
+
A key handler that tests `key == 0x0d` for RETURN never fires — RETURN feels
|
|
910
|
+
"dead" — while letter and space keys work normally. Typically shows up on a
|
|
911
|
+
menu ("press RETURN to start") or a confirm action: the cursor moves, letters
|
|
912
|
+
register, but RETURN does nothing.
|
|
913
|
+
|
|
914
|
+
### Mechanism
|
|
915
|
+
|
|
916
|
+
Oscar64's `conio` input functions (`getch`, `getchx`, `getche`) run the raw key
|
|
917
|
+
through `convch()`. When `iocharmap()` is set to an input map at or above ASCII
|
|
918
|
+
(`IOCHM_ASCII`, `IOCHM_PETSCII_1`, `IOCHM_PETSCII_2`), `convch()` rewrites
|
|
919
|
+
carriage return to line feed:
|
|
920
|
+
|
|
921
|
+
```c
|
|
922
|
+
// Oscar64 include/conio.c (convch)
|
|
923
|
+
if (giocharmap >= IOCHM_ASCII) {
|
|
924
|
+
if (ch == 13) // PETSCII RETURN
|
|
925
|
+
ch = 10; // delivered to your code as $0A
|
|
926
|
+
...
|
|
927
|
+
}
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
`giocharmap` starts at IOCHM_ASCII (`conio.c` line 3: `static IOCharMap
|
|
931
|
+
giocharmap = IOCHM_ASCII;`), so the remap is on unless you call
|
|
932
|
+
`iocharmap(IOCHM_TRANSPARENT)` — a program that never calls `iocharmap()` at
|
|
933
|
+
all is affected. Measured in VICE 3.10 with Oscar64 2026-05-19 (`-keybuf` with
|
|
934
|
+
a newline, which puts PETSCII $0D in the KERNAL buffer): raw GETIN $0D;
|
|
935
|
+
`getchx()` on the default map $0A; after `iocharmap(IOCHM_TRANSPARENT)` $0D;
|
|
936
|
+
after `iocharmap(IOCHM_PETSCII_2)` $0A. An earlier version of this entry named
|
|
937
|
+
only IOCHM_PETSCII_2 as the cause.
|
|
938
|
+
|
|
939
|
+
So a RETURN keypress (PETSCII `$0D`) reaches your code as `$0A`. Any comparison
|
|
940
|
+
against `$0D` silently misses. (The reverse map preserves `$0D` on *output*, so
|
|
941
|
+
printing is unaffected — only keyboard input is remapped.)
|
|
942
|
+
|
|
943
|
+
The bug hides easily when input is also drivable by a test harness that writes
|
|
944
|
+
an action code directly (e.g. a state byte) instead of going through `getchx` —
|
|
945
|
+
the harness path never exercises the key comparison, so RETURN looks fine in
|
|
946
|
+
automated tests but is broken for a real keypress.
|
|
947
|
+
|
|
948
|
+
### Fix
|
|
949
|
+
|
|
950
|
+
Accept `$0A` (or both `$0A` and `$0D`) everywhere you test for RETURN:
|
|
951
|
+
|
|
952
|
+
```c
|
|
953
|
+
// bad — never matches RETURN unless iocharmap(IOCHM_TRANSPARENT) was called
|
|
954
|
+
if (key == 0x0d) start_level();
|
|
955
|
+
|
|
956
|
+
// good
|
|
957
|
+
if (key == 0x0d || key == 0x0a) start_level();
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
In a `switch`, add `case 0x0a:` alongside `case 0x0d:`.
|
|
961
|
+
|
|
962
|
+
The alternative is `iocharmap(IOCHM_TRANSPARENT)`, which disables the
|
|
963
|
+
rewrite — but it also disables the PETSCII case-swap, so use it only if you
|
|
964
|
+
want raw PETSCII throughout.
|
|
965
|
+
|
|
966
|
+
### Cross-references
|
|
967
|
+
|
|
968
|
+
- Oscar64 `include/conio.c` (`convch`); the active map defaults to
|
|
969
|
+
IOCHM_ASCII and is changed by `iocharmap()`; only IOCHM_TRANSPARENT
|
|
970
|
+
disables the $0D→$0A rewrite.
|
|
971
|
+
- Discovered wiring RETURN-to-start on the Tideline level-select screen; the
|
|
972
|
+
select→play path had only ever been exercised via the test-harness action
|
|
973
|
+
byte, which masked the remap.
|
|
974
|
+
|
|
975
|
+
---
|
|
976
|
+
|
|
977
|
+
## restore_nmi_not_maskable — RESTORE drives /NMI directly; masking $DD0D does nothing, only the $0318 vector or an NMI lock neutralises it
|
|
978
|
+
|
|
979
|
+
**Severity:** high
|
|
980
|
+
**Region:** both
|
|
981
|
+
**Triggered by registers:** DD0D
|
|
982
|
+
**Triggered by kernal:** RESTOR, VECTOR
|
|
983
|
+
**Triggered by techniques:** nmi_handler_and_restore_key
|
|
984
|
+
|
|
985
|
+
### Symptom
|
|
986
|
+
|
|
987
|
+
A demo or game drops to a cleared blue screen and `READY.` when the user
|
|
988
|
+
presses RUN/STOP+RESTORE, even though its init wrote `$10` (or `$7F`) to
|
|
989
|
+
`$DD0D` "to switch the RESTORE NMI off". Everything the program held through
|
|
990
|
+
the KERNAL goes with it: `$0314` is back at `$EA31`, the jiffy IRQ is running
|
|
991
|
+
again, `$01` is back to the stock map, the VIC is in text mode at `$0400`. The
|
|
992
|
+
code is still in memory, which is the whole point of the key.
|
|
993
|
+
|
|
994
|
+
RESTORE alone, without RUN/STOP, is quieter: a stable-raster split tears or a
|
|
995
|
+
sprite multiplexer misplaces for one frame per press — the KERNAL's handler
|
|
996
|
+
ran 182 cycles at an unpredictable point in the frame, from the NMI sequence
|
|
997
|
+
to its `RTI`, with no cartridge, `$02A1 = 0` and no key held (rung 1: measured
|
|
998
|
+
in VICE x64sc 3.10 as 189 cycles across a CIA1 Timer A count, 7 of them the
|
|
999
|
+
trampoline described under Mechanism; the instruction path summed by hand
|
|
1000
|
+
from the bytes gives the same 189) — while `$DD0D` reads `$00` and no CIA2
|
|
1001
|
+
source is enabled. A program with its own NMI-timed player is not in this
|
|
1002
|
+
case: it owns `$0318`, so the KERNAL path never runs for it, and a press hands
|
|
1003
|
+
its handler one extra, early entry instead — spurious unless the handler
|
|
1004
|
+
tests bit 7 of `$DD0D` before acting.
|
|
1005
|
+
|
|
1006
|
+
A third form: the protection was there and vanished. The program pointed
|
|
1007
|
+
`$0318` at its own handler, then later ran the customary "put the KERNAL
|
|
1008
|
+
vectors back" line. `JSR $FF8A` (RESTOR) copies the ROM table at `$FD30` and
|
|
1009
|
+
writes `$FE47` back unconditionally. VECTOR with C = 0 (`$FF8D`) installs
|
|
1010
|
+
whatever 32-byte table the caller points at — VECTOR (`$FF8D` → `$FD1A`)
|
|
1011
|
+
copies through a 32-byte loop at `$FD20`, whose C = 0 path is `LDA ($C3),Y`
|
|
1012
|
+
(`$FD25`) `: STA ($C3),Y : STA $0314,Y` (`$FD29`); an earlier draft put the
|
|
1013
|
+
pair at `$FD1A`, which is the entry's `STX $C3` — so it puts `$FE47` back
|
|
1014
|
+
when that table was captured with C = 1
|
|
1015
|
+
before `$0318` was changed, which is the usual snapshot-then-restore idiom.
|
|
1016
|
+
Either way `$0318` is `$FE47` again.
|
|
1017
|
+
|
|
1018
|
+
### Mechanism
|
|
1019
|
+
|
|
1020
|
+
There are two halves, and they stand on different rungs.
|
|
1021
|
+
|
|
1022
|
+
**The wiring — rung 4 for the circuit, rung 2–3 for the part list.** RESTORE
|
|
1023
|
+
is one of the two keys outside the 8×8 matrix that CIA1 scans (SHIFT LOCK is
|
|
1024
|
+
the other, and it is only LSHIFT's wire); the C64-Wiki's keyboard page has it
|
|
1025
|
+
as "tied to the NMI line and not part of the matrix". The switch is coupled
|
|
1026
|
+
through a capacitor, C38, to a monostable whose output pulls the 6510's /NMI
|
|
1027
|
+
pin low for the length of its pulse. CIA2's /IRQ output is on the same pin.
|
|
1028
|
+
The two are in parallel: either can assert /NMI, neither passes through the
|
|
1029
|
+
other, and nothing about the key touches CIA2's FLAG pin, its interrupt
|
|
1030
|
+
control register, or any bit you can write in `$DD0D`. The monostable is one
|
|
1031
|
+
half of the 556 dual timer at U20 on the boards whose parts lists the
|
|
1032
|
+
C64-Wiki's motherboard page carries — ASSY 326298 (1982, schematic 326106),
|
|
1033
|
+
250407 (1983), 250425 (1984) and 250466 (1986, schematic 252278); on the
|
|
1034
|
+
250469 (1987 on) U20 is the 8701 clock generator and the RESTORE one-shot was
|
|
1035
|
+
not traced for this entry. On early boards C38 is 51 pF, small enough that a
|
|
1036
|
+
slow press does not fire the one-shot (the German C64-Wiki's cure is 4.7 nF).
|
|
1037
|
+
No figure for the pulse length is claimed here; it was not measured. The key
|
|
1038
|
+
was not pressed for this entry — `-keybuf` stuffs the KERNAL's keyboard
|
|
1039
|
+
buffer, and RESTORE is not a matrix key — so the circuit itself stands on the
|
|
1040
|
+
C64-Wiki's description; what the KERNAL does with the resulting NMI, below,
|
|
1041
|
+
is from the bytes. An earlier draft cited "drawing 252278, reproduced in the
|
|
1042
|
+
Programmer's Reference Guide": 252278 is the 250466's schematic and the 1982
|
|
1043
|
+
Guide cannot contain it.
|
|
1044
|
+
|
|
1045
|
+
**The KERNAL's side — rung 1, from the bytes of `kernal-901227-03.bin`.**
|
|
1046
|
+
The CPU vector at `$FFFA` holds `$FE43`. What runs from there:
|
|
1047
|
+
|
|
1048
|
+
| Where | Bytes | Does |
|
|
1049
|
+
|---|---|---|
|
|
1050
|
+
| `$FE43` | `78` `6C 18 03` | `SEI`, then `JMP ($0318)`. The `SEI` (2 cycles) is the only thing that runs before the RAM vector — harmless to a handler, since `RTI` restores P. The vector's default is `$FE47` (vector table at `$FD30`). |
|
|
1051
|
+
| `$FE47` | `48 8A 48 98 48` | Push A, X, Y. No BRK test — that is the IRQ dispatcher's job at `$FF48`, not this one's. |
|
|
1052
|
+
| `$FE4C` | `A9 7F 8D 0D DD` | `LDA #$7F : STA $DD0D` — mask every CIA2 source. |
|
|
1053
|
+
| `$FE51` | `AC 0D DD` | `LDY $DD0D` — read the flags, which also clears them. |
|
|
1054
|
+
| `$FE54` | `30 1C` | `BMI $FE72` — bit 7 set means some enabled CIA2 source fired: take the RS-232 path. |
|
|
1055
|
+
| `$FE56` | `20 02 FD` `D0 03` `6C 02 80` | No flag. `JSR $FD02` compares `$8004-$8008` with `CBM80`; on a match, `JMP ($8002)` — a cartridge's warm-start vector. |
|
|
1056
|
+
| `$FE5E` | `20 BC F6` | `JSR $F6BC` — the tail of UDTIM: reads `$DC01` until two reads agree. Bit 7 set (nothing in column 7): store the row in `$91`. Bit 7 clear: re-read with `$DC00 = $BD` (columns 1 and 6, where the SHIFT keys are), write the first row value back to `$DC00` (`$F6D4`), and if anything in those columns is down skip the store (`INX : BNE $F6DC`) — SHIFT held with STOP leaves `$91` unwritten. A fresh hardware sample, not the IRQ scan's leftover. |
|
|
1057
|
+
| `$FE61` | `20 E1 FF` | `JSR $FFE1` — STOP, through `($0328)` = `$F6ED`: `LDA $91 : CMP #$7F`. Z is set only when `$91` is exactly `$7F`: RUN/STOP down and no other column-7 key with it (1, ←, CTRL, 2, SPACE, C=, Q) — a second key in that column clears another bit and blinds the check the same way a wrong `$DC00` does. |
|
|
1058
|
+
| `$FE64` | `D0 0C` | `BNE $FE72` — not held: join the RS-232 path, which finds nothing to do, writes `$02A1` back to `$DD0D` (`$FEB6`-`$FEBB`), pulls Y, X, A and `RTI`s (`$FEBC`-`$FEC1`). |
|
|
1059
|
+
| `$FE66` | `20 15 FD` `20 A3 FD` `20 18 E5` `6C 02 A0` | Held: RESTOR, IOINIT, the screen editor's VIC and screen reset, then `JMP ($A002)` — the BASIC warm start. |
|
|
1060
|
+
|
|
1061
|
+
Read the branch at `$FE54` again. The handler decides "this was RESTORE" by
|
|
1062
|
+
finding **no** CIA2 flag. It never sees the key; it cannot. Masking FLAG — or
|
|
1063
|
+
every source — in `$DD0D` only guarantees that the flag is absent, which *is*
|
|
1064
|
+
the RESTORE case. `$DD0D = $10` is a no-op twice over: bit 7 clear makes it a
|
|
1065
|
+
CLEAR-mask write, so it clears a FLAG mask that IOINIT had already cleared,
|
|
1066
|
+
and the key was never going to raise that flag in the first place.
|
|
1067
|
+
|
|
1068
|
+
The same bytes give one more thing for free. The `LDA #$7F : STA $DD0D` at
|
|
1069
|
+
`$FE4C` wipes whatever CIA2 mask the program had set, and the exit at `$FEB6`
|
|
1070
|
+
re-enables only what `$02A1` — the KERNAL's RS-232 shadow — holds. A program
|
|
1071
|
+
that arms a CIA2 timer NMI while leaving `$0318` at `$FE47` loses that mask
|
|
1072
|
+
on the first RESTORE press or RS-232 event (rung 1, from the bytes; not run).
|
|
1073
|
+
|
|
1074
|
+
Measured (rung 1; VICE x64sc 3.10, PAL C64C: 8565, 8580, 8521 — the build on
|
|
1075
|
+
this machine, the rest of this repository was checked against 3.9; an
|
|
1076
|
+
earlier version said PAL 6569, but `x64sc -default` is the C64C): a CIA2 Timer A one-shot NMI
|
|
1077
|
+
was sent through `$0318` to a trampoline of `BIT $DD0D` and `JMP $FE47`,
|
|
1078
|
+
preceded by a counter increment, with IRQs off and `$91` pre-set to `$00`.
|
|
1079
|
+
After the NMI, `$91`
|
|
1080
|
+
read `$FF`: the KERNAL's handler took the no-flag branch and `$F6BC` wrote the
|
|
1081
|
+
keyboard row into it. The control — the same trampoline without the `BIT` —
|
|
1082
|
+
left `$91` at `$00`: the standing flag sent it down the `BMI`. The screen
|
|
1083
|
+
cells that displayed `$91` were decoded against the character ROM, not read
|
|
1084
|
+
by eye. The warm-start branch itself (`$FE66` onward) was not exercised: it
|
|
1085
|
+
needs RUN/STOP held, and a headless run has no keys.
|
|
1086
|
+
|
|
1087
|
+
**RUN/STOP without the IRQ.** Because `$F6BC` samples the hardware,
|
|
1088
|
+
RUN/STOP+RESTORE warm-starts with IRQs disabled and the keyboard scan stopped.
|
|
1089
|
+
What it does need is `$DC00` still driving column 7 low: IOINIT leaves
|
|
1090
|
+
`$DC00 = $7F` (the store at `$FDAB`) and SCNKEY writes `$7F` back on exit
|
|
1091
|
+
(`$EB42`), so the row read at `$F6BC` sees STOP on bit 7. A program that has
|
|
1092
|
+
left another value in `$DC00` with bit 7 high blinds the check, and
|
|
1093
|
+
RUN/STOP+RESTORE then behaves like RESTORE alone. Read from the bytes; not
|
|
1094
|
+
measured.
|
|
1095
|
+
|
|
1096
|
+
### Fix
|
|
1097
|
+
|
|
1098
|
+
**A — take the vector (`$0318`).** `$FE43` runs `SEI` and then jumps through
|
|
1099
|
+
`$0318`, so a handler there replaces the whole dispatch from its second
|
|
1100
|
+
instruction on. The smallest one is a single `RTI`; from BASIC, `POKE
|
|
1101
|
+
792,193` aims `$0318` at `$FEC1`, which is the `RTI` at the end of the
|
|
1102
|
+
KERNAL's own handler (the byte at `$FEC1` is `$40`). Conditions and costs:
|
|
1103
|
+
|
|
1104
|
+
- Every press still costs 20 cycles — the NMI sequence (7), the `SEI` (2),
|
|
1105
|
+
the `JMP ($0318)` (5) and the `RTI` (6) — at an arbitrary point in the
|
|
1106
|
+
frame; cycle-exact code shows it once per press. Measured (rung 1, VICE
|
|
1107
|
+
x64sc 3.10): CIA1 Timer A counting across a 200-cycle block of `NOP`s with
|
|
1108
|
+
DEN off read `$FF2C` with the CIA2 NMI masked and `$FF18` with it taken,
|
|
1109
|
+
through a RAM `rti` and through the `$FEC1` stub alike — 20 either way. An
|
|
1110
|
+
earlier draft of this entry said 13, having left out the `SEI` and the
|
|
1111
|
+
indirect jump.
|
|
1112
|
+
- The handler gets control with nothing saved. The KERNAL's `PHA : TXA : PHA
|
|
1113
|
+
: TYA : PHA` is at `$FE47`, after the vector, not before it — a handler
|
|
1114
|
+
that does more than `RTI` saves what it touches.
|
|
1115
|
+
- Do not read `$DD0D` in this handler "to be safe" if the program also uses
|
|
1116
|
+
CIA2 NMIs: the read discards a CIA2 flag that may have arrived, and with it
|
|
1117
|
+
the timer or RS-232 event it announced. If both are in play, test bit 7 of
|
|
1118
|
+
`$DD0D` and read it only when you mean to.
|
|
1119
|
+
- It holds only while `$0318` does. RESTOR (`$FF8A`) copies the ROM table
|
|
1120
|
+
and writes `$FE47` back unconditionally, and so does the warm start, which
|
|
1121
|
+
calls it (`$FE66`). VECTOR with C = 0 (`$FF8D`) installs whatever table the
|
|
1122
|
+
caller passes — the usual snapshot-then-restore idiom passes one captured
|
|
1123
|
+
before `$0318` was changed, and that one holds `$FE47`. That is why those
|
|
1124
|
+
two routines are on this entry's Triggered-by line.
|
|
1125
|
+
- It needs the KERNAL ROM mapped in (`$01` bit 1 set). With HIRAM = 0 the CPU
|
|
1126
|
+
fetches `$FFFA` from RAM and the vector is yours to supply there;
|
|
1127
|
+
`ram_under_rom_traps` in `pitfalls/banking.md` shows the pattern.
|
|
1128
|
+
|
|
1129
|
+
**B — the NMI lock.** The 6510's NMI input is edge-sensitive: an NMI is taken
|
|
1130
|
+
on the high-to-low transition of /NMI, and a line that then stays low is not
|
|
1131
|
+
taken again. Arm a CIA2 Timer A one-shot with its NMI mask set and give it a
|
|
1132
|
+
handler that never reads `$DD0D`. The timer underflows once, CIA2 sets its IR
|
|
1133
|
+
bit and pulls /NMI low, the handler runs once — and /NMI then stays low for as
|
|
1134
|
+
long as the flag stands. No later source can make an edge: not a second
|
|
1135
|
+
underflow, not the 556. The lock holds until something reads `$DD0D`.
|
|
1136
|
+
|
|
1137
|
+
Measured (rung 1; VICE x64sc 3.10): with the handler in the listing below, two
|
|
1138
|
+
one-shots produced one NMI (the counter cell showed `1`); the same program
|
|
1139
|
+
with `LDA $DD0D` in the handler produced two (`2`); and the locked program
|
|
1140
|
+
with a single `LDA $DD0D` from the main loop before a third one-shot produced
|
|
1141
|
+
two — the read, and nothing else, re-arms the edge. Costs: every CIA2 NMI is
|
|
1142
|
+
forfeited (RS-232, NMI-timed digi and music players), and any code that reads
|
|
1143
|
+
`$DD0D` — yours, or a KERNAL RS-232 routine — silently unlocks it.
|
|
1144
|
+
|
|
1145
|
+
**What does not work.** Any value written to `$DD0D`; `SEI` (it masks /IRQ
|
|
1146
|
+
only); stopping the KERNAL IRQ scan (the NMI path samples STOP itself).
|
|
1147
|
+
|
|
1148
|
+
### Worked example
|
|
1149
|
+
|
|
1150
|
+
The pattern the registers page used to recommend. It clears a mask that is
|
|
1151
|
+
already clear, and the key never went through the CIA anyway:
|
|
1152
|
+
|
|
1153
|
+
```kick
|
|
1154
|
+
// DOES NOTHING TO RESTORE. Bit 7 clear = CLEAR-mask write; FLAG's mask is
|
|
1155
|
+
// already clear after IOINIT, and RESTORE never raises a CIA2 flag.
|
|
1156
|
+
lda #$10
|
|
1157
|
+
sta $dd0d
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
Fix A. Only the `SEI` at `$FE43` runs between the CPU's vector fetch and
|
|
1161
|
+
`($0318)`, so the stub is the whole story of a RESTORE press:
|
|
1162
|
+
|
|
1163
|
+
```kick
|
|
1164
|
+
// Fix A: take the NMI vector. $FE43 is SEI / JMP ($0318); only the SEI runs
|
|
1165
|
+
// before the vector, and RTI puts P back.
|
|
1166
|
+
install_nmi_stub:
|
|
1167
|
+
sei
|
|
1168
|
+
lda #<nmi_stub
|
|
1169
|
+
sta $0318
|
|
1170
|
+
lda #>nmi_stub
|
|
1171
|
+
sta $0319
|
|
1172
|
+
cli
|
|
1173
|
+
rts
|
|
1174
|
+
|
|
1175
|
+
nmi_stub:
|
|
1176
|
+
rti // a press now costs 20 cycles (7 NMI + 2 SEI
|
|
1177
|
+
// + 5 JMP ind + 6 RTI, measured) and does
|
|
1178
|
+
// nothing (do not add "lda $dd0d" here if
|
|
1179
|
+
// CIA2 NMIs are also in use — it discards
|
|
1180
|
+
// their flag)
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
Fix B, exactly as run and measured. Cell 0 of the screen counts NMIs, cell 1
|
|
1184
|
+
gets a `*` when the program reaches its end; `pause` busy-waits about a
|
|
1185
|
+
second (3 × 256 × 1286 cycles, rung 3). Two one-shots, one NMI:
|
|
1186
|
+
|
|
1187
|
+
```kick
|
|
1188
|
+
// lock.asm — fix B, the NMI lock. Handler never reads $DD0D, so CIA2 keeps
|
|
1189
|
+
// /NMI low; a second Timer A underflow produces no new edge.
|
|
1190
|
+
// Expected: cell 0 = '1', cell 1 = '*'.
|
|
1191
|
+
BasicUpstart2(start)
|
|
1192
|
+
* = $0810
|
|
1193
|
+
start:
|
|
1194
|
+
sei
|
|
1195
|
+
ldx #39
|
|
1196
|
+
lda #$20
|
|
1197
|
+
clr: sta $0400,x
|
|
1198
|
+
dex
|
|
1199
|
+
bpl clr
|
|
1200
|
+
lda #<nmi
|
|
1201
|
+
sta $0318
|
|
1202
|
+
lda #>nmi
|
|
1203
|
+
sta $0319
|
|
1204
|
+
lda #$30 // '0'
|
|
1205
|
+
sta $0400
|
|
1206
|
+
lda #$7f
|
|
1207
|
+
sta $dd0d // mask every CIA2 source
|
|
1208
|
+
lda $dd0d // drop anything pending
|
|
1209
|
+
lda #$ff
|
|
1210
|
+
sta $dd04
|
|
1211
|
+
lda #$00
|
|
1212
|
+
sta $dd05 // Timer A latch = 255: 256 cycles to underflow
|
|
1213
|
+
lda #$81
|
|
1214
|
+
sta $dd0d // Timer A underflow -> /NMI
|
|
1215
|
+
lda #$19
|
|
1216
|
+
sta $dd0e // force load, one-shot, start
|
|
1217
|
+
jsr pause
|
|
1218
|
+
lda #$19
|
|
1219
|
+
sta $dd0e // second one-shot: a second underflow
|
|
1220
|
+
jsr pause
|
|
1221
|
+
lda #$2a // '*' = reached the end
|
|
1222
|
+
sta $0401
|
|
1223
|
+
hang: jmp hang
|
|
1224
|
+
|
|
1225
|
+
nmi: inc $0400
|
|
1226
|
+
rti // no read of $DD0D: /NMI stays low
|
|
1227
|
+
|
|
1228
|
+
pause: lda #3
|
|
1229
|
+
sta $02
|
|
1230
|
+
p1: ldx #0
|
|
1231
|
+
p2: ldy #0
|
|
1232
|
+
p3: dey
|
|
1233
|
+
bne p3
|
|
1234
|
+
dex
|
|
1235
|
+
bne p2
|
|
1236
|
+
dec $02
|
|
1237
|
+
bne p1
|
|
1238
|
+
rts
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
Change the handler to `inc $0400 : pha : lda $dd0d : pla : rti` and the same
|
|
1242
|
+
program shows `2`. That variant is also the shape of fix A when the program
|
|
1243
|
+
has its own CIA2 NMI source: the handler runs, acknowledges, returns, and the
|
|
1244
|
+
main program continues.
|
|
1245
|
+
|
|
1246
|
+
The experiment behind the "absence of a flag" claim. Put this at `$0318`,
|
|
1247
|
+
fire a CIA2 timer NMI, and the KERNAL's own handler cannot tell it from a
|
|
1248
|
+
RESTORE press — with RUN/STOP held it would warm-start:
|
|
1249
|
+
|
|
1250
|
+
```kick
|
|
1251
|
+
tramp: bit $dd0d // clear the CIA2 flag before the KERNAL looks
|
|
1252
|
+
jmp $fe47 // KERNAL NMI handler, as if from RESTORE
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
### Cross-references
|
|
1256
|
+
|
|
1257
|
+
- `hardware/cia-reference.md` → "NMI vector (CIA2 + RESTORE)" — the dispatch
|
|
1258
|
+
summary; its "Disabling RESTORE" paragraph points back here.
|
|
1259
|
+
- `hardware/c64-registers-reference.md` → CIA2 key wiring points and quick
|
|
1260
|
+
lookup — corrected together with this entry; they used to name FLAG bit 4
|
|
1261
|
+
and recommend `$DD0D = $10`.
|
|
1262
|
+
- `hardware/kernal-routines-reference.md` → Vectors table (NMINV `$0318`,
|
|
1263
|
+
ISTOP `$0328`), RESTOR (`$FF8A`), VECTOR (`$FF8D`).
|
|
1264
|
+
- Pitfall `decimal_mode_in_irq_handler` — an NMI handler that does arithmetic
|
|
1265
|
+
needs `CLD` as well.
|
|
1266
|
+
- Pitfall `ram_under_rom_traps` (`pitfalls/banking.md`) — the all-RAM case,
|
|
1267
|
+
where `$FFFA` is yours and the KERNAL dispatch is out of the picture.
|
|
1268
|
+
- Technique `stable_raster_irq` — the routine whose once-per-press jitter is
|
|
1269
|
+
the RESTORE-alone symptom.
|
|
1270
|
+
|
|
1271
|
+
### Sources
|
|
1272
|
+
|
|
1273
|
+
- Commodore 64 KERNAL ROM 901227-03 (`kernal-901227-03.bin` as shipped with
|
|
1274
|
+
VICE), bytes read and disassembled by hand for this entry — rung 1.
|
|
1275
|
+
- VICE x64sc 3.10, headless PAL runs of the listings above and their controls,
|
|
1276
|
+
screen cells decoded against `chargen-901225-01.bin` — rung 1.
|
|
1277
|
+
- C64-Wiki, "RESTORE (Key)", https://www.c64-wiki.com/wiki/RESTORE_(Key) —
|
|
1278
|
+
the direct connection to the CPU, `POKE 792,193`, the edge-triggered lock.
|
|
1279
|
+
- C64-Wiki, "Keyboard", https://www.c64-wiki.com/wiki/Keyboard — RESTORE and
|
|
1280
|
+
SHIFT LOCK outside the matrix.
|
|
1281
|
+
- C64-Wiki (German), "RESTORE (Taste)",
|
|
1282
|
+
https://www.c64-wiki.de/wiki/RESTORE_(Taste) — the capacitor coupling
|
|
1283
|
+
(C38, 51 pF on early boards, 4.7 nF as the cure) and early boards'
|
|
1284
|
+
insensitivity to a slow press.
|
|
1285
|
+
- C64-Wiki, "Motherboard", https://www.c64-wiki.com/wiki/Motherboard
|
|
1286
|
+
(fetched 2026-09-22) — the per-board parts lists: U20 = LM556/NE556 dual
|
|
1287
|
+
timer on ASSY 326298, 250407, 250425 and 250466, U20 = 8701 clock generator
|
|
1288
|
+
on 250469; schematic drawing numbers 326106 (326298) and 252278 (250466).
|
|
1289
|
+
Rung 2–3. An earlier draft of this list cited "drawing 252278, reproduced in
|
|
1290
|
+
the Programmer's Reference Guide", from memory; the Guide is from 1982 and
|
|
1291
|
+
252278 is the 1986 board's drawing.
|
|
1292
|
+
- Joe Forster/STA, "Commodore 64 memory map", https://sta.c64.org/cbm64mem.html
|
|
1293
|
+
— `$0318` default `$FE47`, `$02A1`.
|
|
1294
|
+
|
|
1295
|
+
---
|
|
1296
|
+
|
|
1297
|
+
## raster_irq_during_serial_io — A raster IRQ armed across KERNAL disk I/O misses most frames, and rirq_stop() does not stop it
|
|
1298
|
+
|
|
1299
|
+
**Severity:** medium
|
|
1300
|
+
**Region:** both
|
|
1301
|
+
**Triggered by kernal:** OPEN, CLOSE, CHKIN, CHKOUT, CLRCHN, CHRIN, CHROUT, LOAD, SAVE
|
|
1302
|
+
**Triggered by techniques:** stable_raster_irq, frame_sync_loop, kernal_file_write_seq, kernal_file_read_seq, kernal_relative_file_io, kernal_load_to_address, directory_read_and_select
|
|
1303
|
+
|
|
1304
|
+
### Symptom
|
|
1305
|
+
|
|
1306
|
+
A border split at rows 100 and 200 is clean while the bus is idle. While
|
|
1307
|
+
a 2 KB sequential file is written to drive 8 the split is gone from most
|
|
1308
|
+
frames; when it appears it is a few lines low, or a three-line white
|
|
1309
|
+
sliver near line 240 with the rest of the border in the idle colour. A
|
|
1310
|
+
frame counter kept in the raster handler falls behind by two frames in
|
|
1311
|
+
three. Wrapping the file calls in `rirq_stop()` and `rirq_start()`
|
|
1312
|
+
changes nothing. The file itself is correct: status `$00` after the
|
|
1313
|
+
write, `$40` after the read, the read-back checksum matches.
|
|
1314
|
+
|
|
1315
|
+
Measured in VICE x64sc 3.10 with a true-drive 1541 on a fresh D64, an
|
|
1316
|
+
Oscar64 `rasterirq.h` split (row 100 white, row 200 light blue) and a
|
|
1317
|
+
`rirq_call` handler on the row-100 slot that counts entries and records
|
|
1318
|
+
the lowest and highest `$D012` at entry; the test source is quoted on
|
|
1319
|
+
`recipes/oscar64/high-score-persist.md`, section "A raster IRQ during
|
|
1320
|
+
file I/O". Cycles are CIA2 timers A and B cascaded; frames are those
|
|
1321
|
+
cycles over 19,656 (PAL) or 17,095 (NTSC), arithmetic.
|
|
1322
|
+
|
|
1323
|
+
| Build | Model | Write 2,048 B | Handler entries | `$D012` at entry | Read 2,048 B | Handler entries | `$D012` at entry |
|
|
1324
|
+
|---|---|---|---|---|---|---|---|
|
|
1325
|
+
| IRQ armed | PAL | 9,359,321 cycles, 476 frames | 145 | 101 to 240 | 5,656,966 cycles, 288 frames | 226 | 101 to 234 |
|
|
1326
|
+
| IRQ armed | NTSC | 9,702,621 cycles, 568 frames | 169 | 101 to 241 | 5,991,068 cycles, 350 frames | 268 | 101 to 242 |
|
|
1327
|
+
| `rirq_stop()` around each call | PAL | 9,359,424 cycles, 476 frames | 144 | 101 to 238 | 5,659,833 cycles, 288 frames | 226 | 101 to 233 |
|
|
1328
|
+
| `rirq_stop()` around each call | NTSC | 9,702,713 cycles, 568 frames | 168 | 101 to 240 | 5,983,652 cycles, 350 frames | 270 | 101 to 247 |
|
|
1329
|
+
| `$D01A` cleared around each call | PAL | 9,158,718 cycles, 466 frames | 0 | none | 5,553,710 cycles, 283 frames | 0 | none |
|
|
1330
|
+
| `$D01A` cleared around each call | NTSC | 9,498,969 cycles, 556 frames | 0 | none | 5,765,998 cycles, 337 frames | 0 | none |
|
|
1331
|
+
|
|
1332
|
+
Idle, the same handler entered on line 101 in every one of 50 frames on
|
|
1333
|
+
both models. Every row above ended with status `$00` after the write,
|
|
1334
|
+
`$40` (EOF) after the read, 2,048 bytes back, checksum `F000` matching,
|
|
1335
|
+
and `00, OK` on the error channel.
|
|
1336
|
+
|
|
1337
|
+
### Mechanism
|
|
1338
|
+
|
|
1339
|
+
The KERNAL's serial primitives run each byte with interrupts off. The
|
|
1340
|
+
send routine at `$ED40` opens with `SEI` and exits through `CLI` at
|
|
1341
|
+
`$EDAB`; the receive routine at `$EE13` does the same and exits at
|
|
1342
|
+
`$EE82`; the talk turnaround at `$EDCC` to `$EDDB` is bracketed the same
|
|
1343
|
+
way (ROM `kernal-901227-03.bin`, rung 1; `kernal_assumes_sei_cleared`
|
|
1344
|
+
above has the census). Inside those brackets are wait loops with no
|
|
1345
|
+
timeout: `$ED50` to `$ED5D` wait on DATA for the listener to be ready
|
|
1346
|
+
before each byte, `$EE1B` waits on CLK for the talker, `$EDD6` waits on
|
|
1347
|
+
CLK for the drive to take the bus. Between bytes the drive is doing its
|
|
1348
|
+
own work, and while it is the C64 sits in one of those loops with the I
|
|
1349
|
+
flag set. Under the monitor, the byte that follows a directory lookup
|
|
1350
|
+
spent 1,512,351 cycles between `$ED40` and `$EDAB`, about 77 PAL frames
|
|
1351
|
+
in one `SEI` bracket.
|
|
1352
|
+
|
|
1353
|
+
The VIC raster latch holds one interrupt. A match that lands inside a
|
|
1354
|
+
bracket is delivered at the `CLI`, wherever the beam is by then, which is
|
|
1355
|
+
the handler entering at line 240 for a slot armed at row 100. A second
|
|
1356
|
+
match inside the same bracket is not remembered, which is the 331 PAL
|
|
1357
|
+
frames out of 476 with no entry at all. The `rasterirq.h` slot code spins
|
|
1358
|
+
on `CMP $D012` until the counter passes its row, so a handler that is
|
|
1359
|
+
delivered after the counter has wrapped waits for the next pass of its
|
|
1360
|
+
row and reads 101 again; the recorded maximum is therefore a floor on
|
|
1361
|
+
the lateness, not its extent.
|
|
1362
|
+
|
|
1363
|
+
`rirq_stop()` is one instruction, `SEI` (`rasterirq.c`), and the first
|
|
1364
|
+
serial primitive's `CLI` cancels it. That is why the second pair of rows
|
|
1365
|
+
matches the first. Clearing `$D01A` removes the source instead of masking
|
|
1366
|
+
the CPU, and the transfer then runs about two per cent faster, the
|
|
1367
|
+
handler time it no longer pays.
|
|
1368
|
+
|
|
1369
|
+
The split in the exit screenshot at 8,000,000 cycles, mid-write: IRQ
|
|
1370
|
+
armed on PAL, white on lines 240 to 242 only, the row-100 slot delivered
|
|
1371
|
+
late and the row-200 slot run straight after it; IRQ armed on NTSC, white
|
|
1372
|
+
on lines 104 to 201 against 101 to 200 idle; `$D01A` cleared, one colour
|
|
1373
|
+
for the whole frame, white on PAL and light blue on NTSC, whichever the
|
|
1374
|
+
border held when the register was cleared. The right border changes one
|
|
1375
|
+
row before the left in every split, the store landing mid-line.
|
|
1376
|
+
|
|
1377
|
+
### Fix
|
|
1378
|
+
|
|
1379
|
+
Do not run file I/O under a raster effect you want to keep. For a game,
|
|
1380
|
+
save and load on a static screen with the raster IRQ off, and put the
|
|
1381
|
+
screen and border into the state you want held before the first call:
|
|
1382
|
+
|
|
1383
|
+
```c
|
|
1384
|
+
vic.color_border = VCOL_BLACK; // whatever the static screen wants
|
|
1385
|
+
vic.intr_enable = 0; // $D01A: no raster IRQ source
|
|
1386
|
+
krnio_setnam("HISCORE,S,W");
|
|
1387
|
+
if (krnio_open(2, 8, 2)) {
|
|
1388
|
+
krnio_write(2, buf, sizeof(buf));
|
|
1389
|
+
krnio_close(2);
|
|
1390
|
+
}
|
|
1391
|
+
vic.intr_ctrl = 1; // $D019: drop a match latched meanwhile
|
|
1392
|
+
vic.intr_enable = 1; // re-arm; rasterirq.h resumes on its next row
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
`rirq_stop()` is not a substitute; it cannot outlive the first byte on
|
|
1396
|
+
the bus. If the raster IRQ must stay armed, expect it to enter late or
|
|
1397
|
+
not at all for the length of the transfer, and do not count frames or
|
|
1398
|
+
drive music from it across the calls: the cascaded CIA2 timer the test
|
|
1399
|
+
used kept time, the handler did not. The transfer speed (about 216
|
|
1400
|
+
bytes per second writing and 355 reading, either model, measured above)
|
|
1401
|
+
is the KERNAL's; the IRQ costs it two per cent.
|
|
1402
|
+
|
|
1403
|
+
### Worked example
|
|
1404
|
+
|
|
1405
|
+
```text
|
|
1406
|
+
// BAD: the split is expected to survive the write
|
|
1407
|
+
rirq_stop(); // sei -- undone at $EDAB by the first byte
|
|
1408
|
+
krnio_open(2, 8, 2); krnio_write(2, buf, 2048); krnio_close(2);
|
|
1409
|
+
rirq_start();
|
|
1410
|
+
// measured: 144 handler entries in 476 PAL frames, entry as late as line 238
|
|
1411
|
+
|
|
1412
|
+
// GOOD: take the source away, hold the picture still, put it back after
|
|
1413
|
+
vic.intr_enable = 0;
|
|
1414
|
+
krnio_open(2, 8, 2); krnio_write(2, buf, 2048); krnio_close(2);
|
|
1415
|
+
vic.intr_ctrl = 1; vic.intr_enable = 1;
|
|
1416
|
+
// measured: 0 entries during the write, the split back on line 101 after
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
### Cross-references
|
|
1420
|
+
|
|
1421
|
+
- Pitfall `kernal_assumes_sei_cleared` above: where the `SEI`/`CLI`
|
|
1422
|
+
pairs are; this entry is what they do to a raster IRQ that is armed
|
|
1423
|
+
when the bus is busy.
|
|
1424
|
+
- Technique `stable_raster_irq`, `frame_sync_loop`
|
|
1425
|
+
(`techniques/raster.md`): the schemes whose handler and frame counter
|
|
1426
|
+
this measurement stalled.
|
|
1427
|
+
- Techniques `kernal_file_write_seq`, `kernal_file_read_seq`
|
|
1428
|
+
(`techniques/file-io.md`): the calls that hold the bus.
|
|
1429
|
+
- Recipe `recipes/oscar64/high-score-persist.md`, section "A raster IRQ
|
|
1430
|
+
during file I/O": the test program and the same figures beside the
|
|
1431
|
+
save-file recipe.
|
|
1432
|
+
- `recipes/oscar64/stable-raster-irq.md`, "What `rirq_init` actually
|
|
1433
|
+
does": the dispatcher shares the IRQ line with the CIA jiffy timer,
|
|
1434
|
+
which the brackets above stall in the same way (not measured here).
|
|
1435
|
+
|
|
1436
|
+
### Sources
|
|
1437
|
+
|
|
1438
|
+
- Commodore 64 KERNAL ROM 901227-03 (`kernal-901227-03.bin` as shipped
|
|
1439
|
+
with VICE), `$ED40`-`$EDB9`, `$EDC7`-`$EDDC`, `$EE13`-`$EE84`,
|
|
1440
|
+
disassembled for this entry, rung 1.
|
|
1441
|
+
- VICE x64sc 3.10 `-default` with `-8` and a fresh `TEST,01` D64, PAL and
|
|
1442
|
+
`-model ntsc`, six builds run to completion at 40,000,000 cycles and
|
|
1443
|
+
again to an exit screenshot at 8,000,000; screen cells decoded against
|
|
1444
|
+
`chargen-901225-01.bin`, border colour read down x = 2 and x = 380,
|
|
1445
|
+
rung 1.
|
|
1446
|
+
- Oscar64 `include/c64/rasterirq.c`, `rirq_start` and `rirq_stop`,
|
|
1447
|
+
rung 1.
|
|
1448
|
+
|
|
1449
|
+
---
|
|
1450
|
+
|
|
1451
|
+
## first_open_after_reset_hangs_on_pal — The first file read after reset can hang for ever in the TALK turnaround on PAL under VICE autostart, and a frame wait only moves the phase
|
|
1452
|
+
|
|
1453
|
+
**Severity:** high
|
|
1454
|
+
**Region:** PAL
|
|
1455
|
+
**Triggered by kernal:** OPEN, CHKIN
|
|
1456
|
+
**Triggered by techniques:** kernal_file_read_seq, kernal_file_write_seq, error_channel_check, kernal_load_to_address, directory_read_and_select
|
|
1457
|
+
|
|
1458
|
+
### Symptom
|
|
1459
|
+
|
|
1460
|
+
A program whose first act after its own set-up is to open a file on
|
|
1461
|
+
drive 8 and read it never returns from the read. The screen keeps
|
|
1462
|
+
whatever was drawn before the call; the drive line the program prints
|
|
1463
|
+
on return never appears; a frame counter kept by the main loop stays at
|
|
1464
|
+
zero. The KERNAL status byte `$90` is `$00`, so nothing is reported, and
|
|
1465
|
+
the CPU sits with the I flag set. Left for 30,000,000 cycles it is still
|
|
1466
|
+
there. The same binary on `-model ntsc` runs. A build that waits a
|
|
1467
|
+
different number of frames before the call runs, and so does the same
|
|
1468
|
+
build when the drive loaded it.
|
|
1469
|
+
|
|
1470
|
+
The hang is deterministic for a given binary, model and autostart delay:
|
|
1471
|
+
the build that hung did so in three runs out of three.
|
|
1472
|
+
|
|
1473
|
+
### Mechanism
|
|
1474
|
+
|
|
1475
|
+
What is measured (VICE x64sc 3.10 remote monitor, `break edd6`, first
|
|
1476
|
+
stop, the platformer scaffold built with `-dDISK_WAIT_FRAMES=10`, PAL):
|
|
1477
|
+
|
|
1478
|
+
- The C64 is at `$EDD6` at cycle 4,885,925: the loop after TKSA that
|
|
1479
|
+
waits for the drive to pull CLK low and become the talker. It has no
|
|
1480
|
+
timeout and runs inside the `SEI` bracket that
|
|
1481
|
+
`kernal_assumes_sei_cleared` above describes. `$90` is `$00`, `$BA` is
|
|
1482
|
+
8, `$DD00` reads `$67`: ATN released, CLK released, DATA held low by
|
|
1483
|
+
the C64 as listener.
|
|
1484
|
+
- The drive CPU is at `$E8E5`, and 27 C64 cycles later at `$E8F4`, drive
|
|
1485
|
+
stopwatch 4,959,055 (the 1541 clock is 1.000 MHz to the PAL C64's
|
|
1486
|
+
0.985 MHz, which accounts for the whole difference). `$7A` is 1
|
|
1487
|
+
(talker active), `$79` is 0, `$7C` is 0 (no ATN pending), `$84` and
|
|
1488
|
+
`$85` are `$62` (TALK, secondary address 2), `$1800` reads `$03` then
|
|
1489
|
+
`$01`, the job queue `$00` to `$05` reads `00 00 01 0F 01 00` with
|
|
1490
|
+
every job on track 18, `$22` is 18.
|
|
1491
|
+
- In the drive ROM (`dos1541-325302-01+901229-05.bin`, rung 1) `$E902`
|
|
1492
|
+
to `$E907` is the wait for ATN release; `$E8D7` to `$E8E0` clears the
|
|
1493
|
+
ATN acknowledge bit; `$E8E3` tests the listener flag and `$E8ED` the
|
|
1494
|
+
talker flag; a talker goes through `$E99C` and `$E9AE` to `$E909`,
|
|
1495
|
+
which is TALK, the routine that pulls CLK. So at the stop the drive
|
|
1496
|
+
had already seen ATN go, knew it was the talker, and was a few dozen
|
|
1497
|
+
cycles from pulling CLK.
|
|
1498
|
+
|
|
1499
|
+
Unperturbed, the C64 never sees CLK go low in 30,000,000 cycles. Reading
|
|
1500
|
+
the drive's registers from the monitor at that stop lets the run through
|
|
1501
|
+
(two runs each way on `recipes/oscar64/high-score-persist.md`; this
|
|
1502
|
+
session's monitor run was cut after two stops, so its own outcome is not
|
|
1503
|
+
known). What is inferred from that: the drive state the monitor shows is
|
|
1504
|
+
not the state the drive holds in the unperturbed run. The monitor's read
|
|
1505
|
+
advances the drive in a different step from the emulator's own
|
|
1506
|
+
scheduling, and in that step the drive sees the release. The outcome
|
|
1507
|
+
turns on where the two CPUs stand relative to each other when the TALK
|
|
1508
|
+
byte goes out under ATN, which is why zero and five frames of wait ran,
|
|
1509
|
+
ten hung, twenty and fifty ran, a six-second autostart delay ran, and
|
|
1510
|
+
loading the program through the drive ran. A frame wait is a phase
|
|
1511
|
+
shift, not a settle time: the drive had been in its idle loop for about
|
|
1512
|
+
two seconds by the time of the OPEN in the earlier session's trace, and
|
|
1513
|
+
`-iecreset 1`, which resets the drive with the computer, changed
|
|
1514
|
+
nothing. Whether a real 1541 can be caught the same way, and which side
|
|
1515
|
+
of the emulator's bus model is at fault, are not measured here. The job
|
|
1516
|
+
return code `0F` in slot 3 of the queue was not chased.
|
|
1517
|
+
|
|
1518
|
+
### Fix
|
|
1519
|
+
|
|
1520
|
+
No wait is safe by construction: the smallest wait that ran here was
|
|
1521
|
+
zero frames and the one that hung was ten. What held across every run:
|
|
1522
|
+
|
|
1523
|
+
- Run the binary you ship on PAL under autostart with a fresh disk, once.
|
|
1524
|
+
The hang is deterministic for that binary, so one run answers.
|
|
1525
|
+
- If it hangs, move the phase: a different frame count before the first
|
|
1526
|
+
file call, or the call later in start-up. Every wait other than ten
|
|
1527
|
+
ran here, and so did the ten-frame build with `-autostart-delay 6` and
|
|
1528
|
+
when the drive loaded it. Test again after any change to the code that
|
|
1529
|
+
runs before the call; a change of code size has moved it before.
|
|
1530
|
+
- Check `$90` (READST) and the error channel after the read rather than
|
|
1531
|
+
assuming the read returns. Nothing the program does once it is inside
|
|
1532
|
+
`$EDD6` gets it out: the loop has no timeout and the I flag is set, so
|
|
1533
|
+
a raster or CIA IRQ watchdog cannot fire. An NMI could, but no such
|
|
1534
|
+
watchdog was built or measured here.
|
|
1535
|
+
|
|
1536
|
+
The recipe's `DISK_WAIT_FRAMES 50` default ran in every run tried on two
|
|
1537
|
+
pages, and remains a phase that happens to work for that binary, not a
|
|
1538
|
+
settle time.
|
|
1539
|
+
|
|
1540
|
+
### Worked example
|
|
1541
|
+
|
|
1542
|
+
The platformer scaffold's listing
|
|
1543
|
+
(`recipes/oscar64/platformer-scaffold.md`), built with
|
|
1544
|
+
`oscar64 -tm=c64 -O2 -dDISK_WAIT_FRAMES=N`, run in x64sc 3.10 `-default
|
|
1545
|
+
-warp +sound +autostart-delay-random -autostartprgmode 1` with a fresh
|
|
1546
|
+
`TEST,01` D64 on a true-drive 1541, wobble off, 10,000,000 cycles unless
|
|
1547
|
+
stated, one run per cell unless stated. "Ran" means the top row shows a
|
|
1548
|
+
frame count `F` and the bottom row `DRIVE: 62, FILE NOT FOUND,00,00`;
|
|
1549
|
+
"hung" means `F 00000` and no drive line. Cells decoded against
|
|
1550
|
+
`chargen-901225-01.bin`.
|
|
1551
|
+
|
|
1552
|
+
| Wait before the first OPEN | PAL, PRG injected | NTSC, PRG injected |
|
|
1553
|
+
|---|---|---|
|
|
1554
|
+
| 0 frames (6,457-byte PRG) | ran, F 264 | ran, F 293 |
|
|
1555
|
+
| 5 frames | ran, F 259 | ran, F 287 |
|
|
1556
|
+
| 10 frames | hung, three runs, one of them at 30,000,000 cycles | ran, F 283 |
|
|
1557
|
+
| 20 frames | ran, F 244 | ran, F 273 |
|
|
1558
|
+
| 50 frames | ran, F 215 | ran, F 243 |
|
|
1559
|
+
|
|
1560
|
+
The 10-frame PAL build, one thing changed at a time, one run each:
|
|
1561
|
+
|
|
1562
|
+
| Change | Outcome |
|
|
1563
|
+
|---|---|
|
|
1564
|
+
| `-iecreset 1` | hung |
|
|
1565
|
+
| `-autostart-delay 6` (12,000,000 cycles) | ran, F 206 |
|
|
1566
|
+
| PRG written to the disk and the D64 autostarted, so the drive loaded it (40,000,000 cycles) | ran, F 800, drive line `01, FILES SCRATCHED` |
|
|
1567
|
+
|
|
1568
|
+
Region PAL rests on the five NTSC runs above and the one on the recipe
|
|
1569
|
+
page; no NTSC run has hung, and nothing here says none can.
|
|
1570
|
+
|
|
1571
|
+
```text
|
|
1572
|
+
// BAD: the read is the first thing on the bus and its return is assumed
|
|
1573
|
+
krnio_setnam("HISCORE,S,R");
|
|
1574
|
+
krnio_open(2, 8, 2);
|
|
1575
|
+
n = krnio_read(2, buf, 16); // CHKIN -> TALK, TKSA, then $EDD6 for ever
|
|
1576
|
+
// measured: F 00000 and no drive line at 30,000,000 cycles, PAL, ten-frame build
|
|
1577
|
+
|
|
1578
|
+
// BETTER: a phase that ran for this binary, tested on PAL once, and the
|
|
1579
|
+
// status checked after the read
|
|
1580
|
+
for (char i = 0; i < DISK_WAIT_FRAMES; i++) vic_waitFrame();
|
|
1581
|
+
krnio_setnam("HISCORE,S,R");
|
|
1582
|
+
if (krnio_open(2, 8, 2)) {
|
|
1583
|
+
n = krnio_read(2, buf, 16);
|
|
1584
|
+
st = krnio_status(); // $90: $40 EOF is the good case, $02 timeout
|
|
1585
|
+
}
|
|
1586
|
+
krnio_close(2);
|
|
1587
|
+
// measured: ran at 0, 5, 20 and 50 frames; the wait does not make the read safe
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
### Cross-references
|
|
1591
|
+
|
|
1592
|
+
- `recipes/oscar64/high-score-persist.md`, "A start-up hang seen in
|
|
1593
|
+
VICE, located but not explained": the first trace of the same spin,
|
|
1594
|
+
with the byte timings of the OPEN and the directory search.
|
|
1595
|
+
- `recipes/oscar64/platformer-scaffold.md`, `DISK_WAIT_FRAMES`: the
|
|
1596
|
+
workaround as shipped, and the listing the runs above were built from.
|
|
1597
|
+
- `formats/iec-disk-reference.md`, "Drive-Not-Ready and Timeout Errors":
|
|
1598
|
+
the KERNAL's one timeout and the waits that have none.
|
|
1599
|
+
- Pitfall `kernal_assumes_sei_cleared` above: the `SEI` bracket the spin
|
|
1600
|
+
sits in.
|
|
1601
|
+
- Pitfall `raster_irq_during_serial_io` above: the same loops, seen from
|
|
1602
|
+
the raster IRQ's side.
|
|
1603
|
+
- Technique `error_channel_check` (`techniques/file-io.md`): the status
|
|
1604
|
+
read the fix asks for after every file call.
|
|
1605
|
+
|
|
1606
|
+
### Sources
|
|
1607
|
+
|
|
1608
|
+
- Commodore 1541 DOS ROM 325302-01 + 901229-05 (`dos1541-325302-01+901229-05.bin`
|
|
1609
|
+
as shipped with VICE), `$E8D7`-`$E90B`, disassembled for this entry,
|
|
1610
|
+
rung 1.
|
|
1611
|
+
- Commodore 64 KERNAL ROM 901227-03, `$EDC7`-`$EDDC`, as disassembled for
|
|
1612
|
+
`raster_irq_during_serial_io` above, rung 1.
|
|
1613
|
+
- VICE x64sc 3.10, twenty runs of the platformer scaffold at five wait
|
|
1614
|
+
counts on PAL and NTSC and five variants of the ten-frame PAL build,
|
|
1615
|
+
and one remote-monitor session with `break edd6`, 2026-09-22, rung 1.
|