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,1427 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: io
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- doc-type: technique-reference -->
|
|
6
|
+
|
|
7
|
+
# File I/O Techniques
|
|
8
|
+
|
|
9
|
+
Reading and writing files on a serial-bus disk drive through the KERNAL's
|
|
10
|
+
logical-file layer: SETLFS, SETNAM, OPEN, the CHKIN/CHKOUT redirection
|
|
11
|
+
pair, CHRIN/CHROUT, READST, CLRCHN, CLOSE and LOAD. The register
|
|
12
|
+
contracts for each routine are in `hardware/kernal-routines-reference.md`;
|
|
13
|
+
the drive-side command set and the secondary-address table are in
|
|
14
|
+
`formats/iec-disk-reference.md`; the ways the KERNAL bites are in
|
|
15
|
+
`pitfalls/kernal-and-io.md`. Every number below that came from an
|
|
16
|
+
instrument says so. The instrument was VICE 3.10 x64sc with true drive
|
|
17
|
+
emulation of a 1541 (the `-default` configuration) and a disk freshly
|
|
18
|
+
formatted by c1541; a real 1541 was not on the bench.
|
|
19
|
+
|
|
20
|
+
Two facts run through every technique below. First, a disk OPEN does not
|
|
21
|
+
report drive-side errors: OPEN returns C=0 for a name that is not on the
|
|
22
|
+
disk (measured: OPEN of `SCORES,S,R` on an empty disk returned C=0,
|
|
23
|
+
CHKIN C=0, and the first CHRIN left ST = `$42`) and for a write to a
|
|
24
|
+
name that already is (measured, see error_channel_check); only the
|
|
25
|
+
drive's error channel knows. Second, the
|
|
26
|
+
KERNAL talks to the drive at the bus's own pace, so these calls take
|
|
27
|
+
real time. The recipe below spends 4,846,078 cycles on the drive for a
|
|
28
|
+
32-byte write, a 32-byte read and two status reads, about 4.9 s at PAL
|
|
29
|
+
speed (measured with a CIA2 timer inside the program; rung 1).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## kernal_file_write_seq — Write a sequential file with OPEN/CHKOUT/CHROUT
|
|
34
|
+
|
|
35
|
+
**Complexity:** low
|
|
36
|
+
**Region:** both
|
|
37
|
+
**Uses registers:** (none)
|
|
38
|
+
**Uses kernal:** SETLFS, SETNAM, OPEN, CHKOUT, CHROUT, CLRCHN, CLOSE
|
|
39
|
+
|
|
40
|
+
### Why
|
|
41
|
+
|
|
42
|
+
A game that keeps a high-score table, a save slot or a level editor's
|
|
43
|
+
output needs to put a run of bytes on a disk under a name, and get the
|
|
44
|
+
same bytes back later. The KERNAL's sequential-file protocol does that
|
|
45
|
+
with seven calls and no drive code of its own. It is the same path
|
|
46
|
+
BASIC's `OPEN 2,8,2,"NAME,S,W"` / `PRINT#2` / `CLOSE 2` takes.
|
|
47
|
+
|
|
48
|
+
### How
|
|
49
|
+
|
|
50
|
+
1. SETLFS with A = a logical file number of your choosing (1 to 127),
|
|
51
|
+
X = 8 for the first drive, Y = a secondary address from 2 to 14. The
|
|
52
|
+
secondary address is the drive's channel number; 0 and 1 are LOAD and
|
|
53
|
+
SAVE, 15 is the command channel.
|
|
54
|
+
2. SETNAM with the length and address of the name string. The suffix
|
|
55
|
+
`,S,W` names the type (sequential) and the mode (write). The string is
|
|
56
|
+
not copied: it must still be there when OPEN runs.
|
|
57
|
+
3. OPEN. Test the carry: C=1 with A = 1, 2, 5 or 6 is a KERNAL-side
|
|
58
|
+
refusal (table full, number already open, no device, number 0). C=0
|
|
59
|
+
says nothing about the drive.
|
|
60
|
+
4. CHKOUT with X = the logical file number. From here every CHROUT byte
|
|
61
|
+
goes to the file instead of the screen.
|
|
62
|
+
5. CHROUT once per byte. A sequential file is byte-transparent: `$00`
|
|
63
|
+
and `$0D` go in and come back unchanged (measured: the recipe's
|
|
64
|
+
payload contains both).
|
|
65
|
+
6. CLRCHN, then CLOSE. CLRCHN first: it sends UNLISTEN and puts CHROUT
|
|
66
|
+
back on the screen; CLOSE then tells the drive to finish the file.
|
|
67
|
+
The other order leaves output pointed at a closed file.
|
|
68
|
+
|
|
69
|
+
```asm
|
|
70
|
+
// kernal_file_write_seq: create SCORES as a SEQ file and write 32 bytes
|
|
71
|
+
.const SETLFS = $ffba
|
|
72
|
+
.const SETNAM = $ffbd
|
|
73
|
+
.const OPEN = $ffc0
|
|
74
|
+
.const CLOSE = $ffc3
|
|
75
|
+
.const CHKOUT = $ffc9
|
|
76
|
+
.const CLRCHN = $ffcc
|
|
77
|
+
.const CHROUT = $ffd2
|
|
78
|
+
|
|
79
|
+
* = $c000
|
|
80
|
+
write_scores:
|
|
81
|
+
// scratch any old copy first: the command goes out as OPEN's filename
|
|
82
|
+
lda #15
|
|
83
|
+
ldx #8
|
|
84
|
+
ldy #15 // secondary 15 = command channel
|
|
85
|
+
jsr SETLFS
|
|
86
|
+
lda #scr_end-scr
|
|
87
|
+
ldx #<scr
|
|
88
|
+
ldy #>scr
|
|
89
|
+
jsr SETNAM
|
|
90
|
+
jsr OPEN // sends S0:SCORES to the drive
|
|
91
|
+
lda #15
|
|
92
|
+
jsr CLOSE
|
|
93
|
+
|
|
94
|
+
lda #2 // logical file 2
|
|
95
|
+
ldx #8 // device 8
|
|
96
|
+
ldy #2 // secondary 2..14 = a data channel
|
|
97
|
+
jsr SETLFS
|
|
98
|
+
lda #wname_end-wname
|
|
99
|
+
ldx #<wname
|
|
100
|
+
ldy #>wname
|
|
101
|
+
jsr SETNAM
|
|
102
|
+
jsr OPEN
|
|
103
|
+
bcs open_failed // C=1: KERNAL-side error, A = code
|
|
104
|
+
ldx #2
|
|
105
|
+
jsr CHKOUT // from here CHROUT writes to the file
|
|
106
|
+
bcs open_failed
|
|
107
|
+
ldy #0
|
|
108
|
+
put:
|
|
109
|
+
lda payload,y
|
|
110
|
+
jsr CHROUT
|
|
111
|
+
iny
|
|
112
|
+
cpy #32
|
|
113
|
+
bne put
|
|
114
|
+
jsr CLRCHN // output back to the screen FIRST
|
|
115
|
+
lda #2
|
|
116
|
+
jsr CLOSE // now the drive finalises the entry
|
|
117
|
+
clc
|
|
118
|
+
rts
|
|
119
|
+
open_failed:
|
|
120
|
+
jsr CLRCHN
|
|
121
|
+
lda #2
|
|
122
|
+
jsr CLOSE // a failed OPEN can leave a table entry
|
|
123
|
+
sec
|
|
124
|
+
rts
|
|
125
|
+
|
|
126
|
+
.encoding "petscii_upper" // a drive reads PETSCII, not screen codes
|
|
127
|
+
scr: .text "S0:SCORES"
|
|
128
|
+
scr_end:
|
|
129
|
+
wname: .text "SCORES,S,W" // name, type S, mode W
|
|
130
|
+
wname_end:
|
|
131
|
+
payload: .fill 32, i
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Why it works
|
|
135
|
+
|
|
136
|
+
OPEN puts the drive into LISTEN with secondary `$F0 | 2` and sends the
|
|
137
|
+
name; the drive parses `,S,W`, allocates a first data block and keeps the
|
|
138
|
+
channel open. CHKOUT sends LISTEN with `$60 | 2`, after which the KERNAL's
|
|
139
|
+
CHROUT vector writes each byte to the bus with IECOUT. CLOSE sends
|
|
140
|
+
`$E0 | 2`, and it is on receiving that close that the drive writes out
|
|
141
|
+
the last partial block, fills in the block count and clears the "open"
|
|
142
|
+
flag in the directory entry. Until then the entry is a splat file: a
|
|
143
|
+
`*SEQ` with 0 blocks that no OPEN can read. That is also what an
|
|
144
|
+
emulator run stopped before CLOSE finished leaves behind; the
|
|
145
|
+
`krnio_save_leaves_splat_file` pitfall shows the same signature for SAVE.
|
|
146
|
+
|
|
147
|
+
### Overwriting
|
|
148
|
+
|
|
149
|
+
Opening `NAME,S,W` when NAME exists is refused by the drive. The OPEN
|
|
150
|
+
still returns C=0 and the error channel reads `63, FILE EXISTS,00,00`
|
|
151
|
+
(measured with a PRG of that name: OPEN of `BLOB,P,W` returned C=0 and
|
|
152
|
+
the channel read 63). What CHKOUT and CHROUT do on that channel
|
|
153
|
+
afterwards was not measured here. Two ways round it:
|
|
154
|
+
|
|
155
|
+
- `@0:NAME,S,W` asks the DOS to replace the file in place. 1541 DOS 2.6's
|
|
156
|
+
save-with-replace has a long-reported defect that can corrupt the disk
|
|
157
|
+
when the drive's buffers are in a particular state; that defect is not
|
|
158
|
+
measured here (rung 4), and this page does not recommend relying on
|
|
159
|
+
the command.
|
|
160
|
+
- Scratch, then write: send `S0:NAME` over the command channel as the
|
|
161
|
+
fragment does, and then OPEN for write. It costs one extra OPEN/CLOSE
|
|
162
|
+
pair, the result is visible on the error channel (`01, FILES
|
|
163
|
+
SCRATCHED,01,00` reports the count; not measured here), and a
|
|
164
|
+
scratch of a name that does not exist is harmless. This is the safer
|
|
165
|
+
habit and the one the fragment uses.
|
|
166
|
+
|
|
167
|
+
### Variations
|
|
168
|
+
|
|
169
|
+
- **Logical file numbers 128 to 255** are safe here. CHROUT adds no
|
|
170
|
+
linefeed whatever the number; the linefeed-after-CR rule belongs to
|
|
171
|
+
BASIC's PRINT#, as `hardware/kernal-routines-reference.md` measured.
|
|
172
|
+
- **PRG rather than SEQ.** `,P,W` writes a PRG; the two-byte load address
|
|
173
|
+
is then yours to CHROUT first. Use SAVE instead when the bytes are
|
|
174
|
+
already contiguous in memory.
|
|
175
|
+
- **Text files for other software.** Write `$0D` after each line; the
|
|
176
|
+
drive stores it as an ordinary byte.
|
|
177
|
+
|
|
178
|
+
### Recipes
|
|
179
|
+
|
|
180
|
+
- `recipes/kickassembler/file-io-roundtrip.md`
|
|
181
|
+
- `recipes/oscar64/save-load-seq-file.md` (the same sequence through Oscar64's kernalio.h, with a provoked 62)
|
|
182
|
+
- `recipes/oscar64/high-score-persist.md` (the policy around the calls: first run, scratch-then-write, version byte, no drive; 74 and the scratch reply measured)
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## kernal_file_read_seq — Read a sequential file with CHKIN/CHRIN/READST
|
|
187
|
+
|
|
188
|
+
**Complexity:** low
|
|
189
|
+
**Region:** both
|
|
190
|
+
**Uses registers:** (none)
|
|
191
|
+
**Uses kernal:** SETLFS, SETNAM, OPEN, CHKIN, CHRIN, READST, CLRCHN, CLOSE
|
|
192
|
+
|
|
193
|
+
### Why
|
|
194
|
+
|
|
195
|
+
The read half of kernal_file_write_seq. The trap is end-of-file: CHRIN
|
|
196
|
+
delivers the last byte with the carry clear, exactly as it delivered
|
|
197
|
+
every earlier one, and the only signal that the byte was the last is bit
|
|
198
|
+
6 of the status byte. A loop that waits for the carry reads one byte too
|
|
199
|
+
many.
|
|
200
|
+
|
|
201
|
+
### How
|
|
202
|
+
|
|
203
|
+
1. SETLFS, SETNAM with `,S,R`, OPEN, as for writing. OPEN returns C=0
|
|
204
|
+
whether or not the file exists.
|
|
205
|
+
2. CHKIN with X = the logical file number. From here CHRIN reads from the
|
|
206
|
+
file.
|
|
207
|
+
3. CHRIN, store the byte, then READST. Zero means more bytes follow.
|
|
208
|
+
`$40` means the byte just stored was the last one. Any other set bit
|
|
209
|
+
is an error: `$42` (read timeout plus EOI) is what a missing file
|
|
210
|
+
looks like, because the drive has nothing to say on that channel
|
|
211
|
+
(measured: `SCORES,S,R` with no such file gave OPEN C=0, CHKIN C=0
|
|
212
|
+
and ST = `$42` on the first CHRIN; VICE 3.10, empty disk).
|
|
213
|
+
4. Bound the loop by your buffer size as well as by ST.
|
|
214
|
+
5. CLRCHN, then CLOSE.
|
|
215
|
+
|
|
216
|
+
```asm
|
|
217
|
+
// kernal_file_read_seq: read SCORES back until the drive signals EOI
|
|
218
|
+
.const SETLFS = $ffba
|
|
219
|
+
.const SETNAM = $ffbd
|
|
220
|
+
.const OPEN = $ffc0
|
|
221
|
+
.const CLOSE = $ffc3
|
|
222
|
+
.const CHKIN = $ffc6
|
|
223
|
+
.const CLRCHN = $ffcc
|
|
224
|
+
.const CHRIN = $ffcf
|
|
225
|
+
.const READST = $ffb7
|
|
226
|
+
|
|
227
|
+
* = $c000
|
|
228
|
+
read_scores:
|
|
229
|
+
lda #3
|
|
230
|
+
ldx #8
|
|
231
|
+
ldy #3
|
|
232
|
+
jsr SETLFS
|
|
233
|
+
lda #rname_end-rname
|
|
234
|
+
ldx #<rname
|
|
235
|
+
ldy #>rname
|
|
236
|
+
jsr SETNAM
|
|
237
|
+
jsr OPEN // C=0 even when the file is missing
|
|
238
|
+
bcs read_failed
|
|
239
|
+
ldx #3
|
|
240
|
+
jsr CHKIN // from here CHRIN reads from the file
|
|
241
|
+
bcs read_failed
|
|
242
|
+
ldy #0
|
|
243
|
+
get:
|
|
244
|
+
jsr CHRIN // one byte, blocking
|
|
245
|
+
sta buffer,y
|
|
246
|
+
iny
|
|
247
|
+
jsr READST // ST: 0 = more, $40 = that was the last
|
|
248
|
+
bne last_or_error
|
|
249
|
+
cpy #64 // never overrun the buffer
|
|
250
|
+
bne get
|
|
251
|
+
last_or_error:
|
|
252
|
+
sty count
|
|
253
|
+
and #$bf // anything but the EOI bit is an error
|
|
254
|
+
bne read_failed // ($42 = read timeout: no such file)
|
|
255
|
+
jsr CLRCHN
|
|
256
|
+
lda #3
|
|
257
|
+
jsr CLOSE
|
|
258
|
+
clc
|
|
259
|
+
rts
|
|
260
|
+
read_failed:
|
|
261
|
+
jsr CLRCHN
|
|
262
|
+
lda #3
|
|
263
|
+
jsr CLOSE
|
|
264
|
+
sec
|
|
265
|
+
rts
|
|
266
|
+
|
|
267
|
+
.encoding "petscii_upper"
|
|
268
|
+
rname: .text "SCORES,S,R"
|
|
269
|
+
rname_end:
|
|
270
|
+
count: .byte 0
|
|
271
|
+
buffer: .fill 64, 0
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Why it works
|
|
275
|
+
|
|
276
|
+
CHKIN sends TALK with `$60 | 3` and turns the bus round so the drive
|
|
277
|
+
drives DATA. Each CHRIN then runs IECIN, which receives one byte and
|
|
278
|
+
notices EOI, the talker's "this is the last byte" signal described under
|
|
279
|
+
EOI in `formats/iec-disk-reference.md`; IECIN records it as bit 6 of
|
|
280
|
+
`$90`. READST returns `$90` unchanged (ROM `$FE1A`: it reads and rewrites
|
|
281
|
+
the byte; the read-and-clear applies only to the RS-232 status at
|
|
282
|
+
`$0297`). The byte is zeroed elsewhere in the ROM: `LDA #0 / STA $90`
|
|
283
|
+
sits at `$F30F`, `$F3DD` and `$F4A7` (rung 1, byte search of
|
|
284
|
+
kernal-901227-03), which is why a fresh OPEN or LOAD starts from ST = 0;
|
|
285
|
+
which routine owns each of those three was not traced here. The recipe's
|
|
286
|
+
readback of a 32-byte file
|
|
287
|
+
returned 32 bytes with ST = `$40` on the thirty-second (measured). The
|
|
288
|
+
status-byte table and the carry-versus-READST explanation are in
|
|
289
|
+
`hardware/kernal-routines-reference.md` under READST and "Status-byte
|
|
290
|
+
interaction with file I/O".
|
|
291
|
+
|
|
292
|
+
### Variations
|
|
293
|
+
|
|
294
|
+
- **GETIN instead of CHRIN.** On a serial channel GETIN goes through the
|
|
295
|
+
same path; use CHRIN, which is the documented blocking read.
|
|
296
|
+
- **Counted reads.** If the writer stored a length first, read it and
|
|
297
|
+
loop on the count; still test READST each time so a short file cannot
|
|
298
|
+
hang the loop.
|
|
299
|
+
- **Two channels at once.** A second file on secondary 4 with its own
|
|
300
|
+
logical number can stay open while this one reads; switch with
|
|
301
|
+
CHKIN/CHKOUT and CLRCHN between them.
|
|
302
|
+
|
|
303
|
+
### Recipes
|
|
304
|
+
|
|
305
|
+
- `recipes/kickassembler/file-io-roundtrip.md`
|
|
306
|
+
- `recipes/oscar64/save-load-seq-file.md` (the same sequence through Oscar64's kernalio.h, with a provoked 62)
|
|
307
|
+
- `recipes/oscar64/high-score-persist.md` (the policy around the calls: first run, scratch-then-write, version byte, no drive; 74 and the scratch reply measured)
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## error_channel_check — Read the drive's status line from channel 15
|
|
312
|
+
|
|
313
|
+
**Complexity:** low
|
|
314
|
+
**Region:** both
|
|
315
|
+
**Uses registers:** (none)
|
|
316
|
+
**Uses kernal:** SETLFS, SETNAM, OPEN, CHKIN, CHRIN, READST, CLRCHN, CLOSE
|
|
317
|
+
|
|
318
|
+
### Why
|
|
319
|
+
|
|
320
|
+
The drive keeps one status line and the KERNAL never reads it for you.
|
|
321
|
+
A disk OPEN returns C=0 for a name that is not there and for a write to
|
|
322
|
+
a name that is; LOAD reports a missing file as A=4 but says nothing
|
|
323
|
+
about a full or protected disk. The status line is the only place these
|
|
324
|
+
answers live, so a program reads it after any operation whose outcome it
|
|
325
|
+
cares about.
|
|
326
|
+
|
|
327
|
+
### How
|
|
328
|
+
|
|
329
|
+
1. SETLFS with logical file 15, device 8, secondary 15. SETNAM with
|
|
330
|
+
length 0: opening the channel bare sends no command.
|
|
331
|
+
2. OPEN. Here C=1 with A=5 does mean something: no device answered.
|
|
332
|
+
3. CHKIN 15, then CHRIN/READST as in kernal_file_read_seq. The line ends
|
|
333
|
+
with a CR and the drive raises EOI on that CR. The longest line seen
|
|
334
|
+
in these runs was 27 bytes including the CR; 40 is a comfortable
|
|
335
|
+
buffer (every message in the ROM's table, read from the drive ROM on this
|
|
336
|
+
machine, is in `../formats/iec-disk-reference.md`, "The 1541 DOS Error
|
|
337
|
+
Codes").
|
|
338
|
+
4. CLRCHN, CLOSE 15. Reading the line is what resets it to `00, OK,00,00`
|
|
339
|
+
(the DOS rule; not measured separately here).
|
|
340
|
+
|
|
341
|
+
```asm
|
|
342
|
+
// error_channel_check: read the drive's status line into stbuf
|
|
343
|
+
.const SETLFS = $ffba
|
|
344
|
+
.const SETNAM = $ffbd
|
|
345
|
+
.const OPEN = $ffc0
|
|
346
|
+
.const CLOSE = $ffc3
|
|
347
|
+
.const CHKIN = $ffc6
|
|
348
|
+
.const CLRCHN = $ffcc
|
|
349
|
+
.const CHRIN = $ffcf
|
|
350
|
+
.const READST = $ffb7
|
|
351
|
+
|
|
352
|
+
* = $c000
|
|
353
|
+
read_status:
|
|
354
|
+
lda #15
|
|
355
|
+
ldx #8
|
|
356
|
+
ldy #15 // secondary 15 = command channel
|
|
357
|
+
jsr SETLFS
|
|
358
|
+
lda #0 // no filename: open the channel bare
|
|
359
|
+
jsr SETNAM
|
|
360
|
+
jsr OPEN
|
|
361
|
+
bcs no_drive // C=1, A=5: nothing answered on the bus
|
|
362
|
+
ldx #15
|
|
363
|
+
jsr CHKIN
|
|
364
|
+
bcs no_drive
|
|
365
|
+
ldy #0
|
|
366
|
+
get:
|
|
367
|
+
jsr CHRIN
|
|
368
|
+
sta stbuf,y
|
|
369
|
+
iny
|
|
370
|
+
jsr READST
|
|
371
|
+
bne done // EOI comes with the closing CR
|
|
372
|
+
cpy #39
|
|
373
|
+
bne get
|
|
374
|
+
done:
|
|
375
|
+
sty stlen
|
|
376
|
+
jsr CLRCHN
|
|
377
|
+
lda #15
|
|
378
|
+
jsr CLOSE
|
|
379
|
+
lda stbuf // first digit of the code, PETSCII
|
|
380
|
+
cmp #$31 // C=0 for 00 OK and 01 FILES SCRATCHED,
|
|
381
|
+
rts // C=1 for anything from 20 upwards
|
|
382
|
+
no_drive:
|
|
383
|
+
jsr CLRCHN
|
|
384
|
+
lda #15
|
|
385
|
+
jsr CLOSE
|
|
386
|
+
sec
|
|
387
|
+
rts
|
|
388
|
+
|
|
389
|
+
stlen: .byte 0
|
|
390
|
+
stbuf: .fill 40, 0 // "62, FILE NOT FOUND,00,00" + CR fits
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The line is `code,message,track,sector` in PETSCII with the two-digit
|
|
394
|
+
code first, so the first byte alone tells you whether to look further.
|
|
395
|
+
|
|
396
|
+
### When to read it
|
|
397
|
+
|
|
398
|
+
- **Once at start, to clear the power-on message.** The first read after
|
|
399
|
+
the drive resets returns `73,CBM DOS V2.6 1541,00,00` (measured; VICE's
|
|
400
|
+
1541-II ROM). A program that treats any non-zero code as failure will
|
|
401
|
+
refuse a healthy drive unless it reads this line first.
|
|
402
|
+
- **After CLOSE of a written file**, which is when a full disk or a
|
|
403
|
+
refused name shows up.
|
|
404
|
+
- **After an OPEN for read** whose CHRIN loop ended with ST other than
|
|
405
|
+
`$40`.
|
|
406
|
+
- **After a LOAD that returned C=1**, to tell 62 from a disk error.
|
|
407
|
+
|
|
408
|
+
### The codes an agent meets first
|
|
409
|
+
|
|
410
|
+
Measured in this page's runs: `00, OK,00,00` after a clean write and
|
|
411
|
+
read; `73,CBM DOS V2.6 1541,00,00` on the first read; `62, FILE NOT
|
|
412
|
+
FOUND,00,00` after LOAD of a name that was not on the disk (LOAD itself
|
|
413
|
+
returned C=1, A=4); `63, FILE EXISTS,00,00` after OPEN of `,P,W` on an
|
|
414
|
+
existing name (OPEN itself returned C=0).
|
|
415
|
+
|
|
416
|
+
Measured since, in `../recipes/oscar64/high-score-persist.md`: `74,DRIVE
|
|
417
|
+
NOT READY,00,00` with no disk attached and `01, FILES SCRATCHED,01,00`
|
|
418
|
+
after a scratch, where the track field is the count. Not measured here,
|
|
419
|
+
from the 1541 DOS message set (rung 4): `26, WRITE PROTECT ON` when the
|
|
420
|
+
notch is covered; `72, DISK FULL` when the last block goes. Codes 20 to
|
|
421
|
+
29 are read or write errors with the track and sector filled in. The
|
|
422
|
+
full table of codes, message text as the ROM spells it, causes and an
|
|
423
|
+
agent's class for each (retry, media, user error, program bug), read
|
|
424
|
+
from the 1541 ROM with four codes provoked in VICE, is
|
|
425
|
+
`../formats/iec-disk-reference.md`, "The 1541 DOS Error Codes", with
|
|
426
|
+
the recipe `../recipes/kickassembler/dos-error-codes.md`.
|
|
427
|
+
|
|
428
|
+
### Variations
|
|
429
|
+
|
|
430
|
+
- **Send a command and read the answer in one open.** SETNAM the command
|
|
431
|
+
(`S0:NAME`, `I0`, `V0`), OPEN, then CHKIN and read: the reply to that
|
|
432
|
+
command comes back on the same channel.
|
|
433
|
+
- **Keep channel 15 open for the program's life** and only CHKIN it when
|
|
434
|
+
needed. This holds one of the KERNAL's ten table slots and one of the
|
|
435
|
+
drive's channels, which is fine for a game.
|
|
436
|
+
- **Hang on a missing drive.** CHKIN on a device that does not answer
|
|
437
|
+
hangs with no timeout, per `hardware/kernal-routines-reference.md`;
|
|
438
|
+
the OPEN's C=1, A=5 return is the last chance to notice, so test it.
|
|
439
|
+
|
|
440
|
+
### Recipes
|
|
441
|
+
|
|
442
|
+
- `recipes/kickassembler/file-io-roundtrip.md`
|
|
443
|
+
- `recipes/oscar64/save-load-seq-file.md` (the same sequence through Oscar64's kernalio.h, with a provoked 62)
|
|
444
|
+
- `recipes/oscar64/high-score-persist.md` (the policy around the calls: first run, scratch-then-write, version byte, no drive; 74 and the scratch reply measured)
|
|
445
|
+
- `recipes/oscar64/relative-file-records.md` (the P command's 50 reply read next to the KERNAL status byte; 51 in a side run)
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## directory_read_and_select — Read the disk directory into a table and pick an entry
|
|
450
|
+
|
|
451
|
+
**Complexity:** low
|
|
452
|
+
**Region:** both
|
|
453
|
+
**Uses registers:** (none)
|
|
454
|
+
**Uses kernal:** SETLFS, SETNAM, OPEN, CHKIN, CHRIN, CLRCHN, CLOSE, READST
|
|
455
|
+
**Requires:** kernal_file_read_seq
|
|
456
|
+
**Cost:** bytes_data=1539
|
|
457
|
+
**Cost basis:** derived-listing
|
|
458
|
+
|
|
459
|
+
### Why
|
|
460
|
+
|
|
461
|
+
A loader menu, a level chooser or a save slot picker needs to know
|
|
462
|
+
what is on the disk. The DOS will tell you: the name `$` opens a
|
|
463
|
+
listing of the directory as if it were a file, and the same CHKIN and
|
|
464
|
+
CHRIN loop that reads a SEQ file reads it. What comes back is not
|
|
465
|
+
text. It is a BASIC program image, with link bytes and line numbers,
|
|
466
|
+
and the block counts live in the line numbers. A parser that knows
|
|
467
|
+
that turns the stream into a table in one pass, and a highlight moved
|
|
468
|
+
by the joystick over that table is the menu.
|
|
469
|
+
|
|
470
|
+
The trap that makes this worth an entry: `LOAD "$",8` from BASIC puts
|
|
471
|
+
that program image where the BASIC program was, so a BASIC program
|
|
472
|
+
that lists the directory that way has replaced itself. Machine code
|
|
473
|
+
and C programs never meet the trap, because OPEN and CHRIN load
|
|
474
|
+
nothing; the bytes go only where the program puts them.
|
|
475
|
+
|
|
476
|
+
### How
|
|
477
|
+
|
|
478
|
+
1. SETLFS with your logical file number, device 8 and secondary
|
|
479
|
+
address 0. SETNAM with the one byte `$`. OPEN. Test the carry:
|
|
480
|
+
C=1 with A=5 is device not present.
|
|
481
|
+
2. CHKIN with X = the logical file number. From here CHRIN reads the
|
|
482
|
+
listing.
|
|
483
|
+
3. The first two bytes are a load address (`$0401`). Skip them.
|
|
484
|
+
4. Each line is two link bytes, a two-byte line number low byte first,
|
|
485
|
+
PETSCII text and a zero. Two zero link bytes end the listing. The
|
|
486
|
+
link bytes carry nothing; skip them. The line number is the drive
|
|
487
|
+
number on the first line, the block count on every entry, and the
|
|
488
|
+
free block count on the last line.
|
|
489
|
+
5. The first line is the header: `$12` (reverse on), the disk name in
|
|
490
|
+
quotes padded to sixteen characters with `$A0`, then the id. An
|
|
491
|
+
entry's text is spaces, the name in quotes, spaces, an optional `*`
|
|
492
|
+
(a file never closed), the three type letters `PRG`, `SEQ`, `REL`,
|
|
493
|
+
`USR` or `DEL`, and an optional `<` (locked). The last line has no
|
|
494
|
+
quotes and reads `BLOCKS FREE.`
|
|
495
|
+
6. Call READST after every CHRIN. Zero means more; `$40` arrives with
|
|
496
|
+
the last byte; anything else is an error and the loop stops.
|
|
497
|
+
7. CLRCHN, then CLOSE. Do this before the next file operation, since
|
|
498
|
+
the drive keeps the directory channel until the listing is consumed
|
|
499
|
+
or closed.
|
|
500
|
+
8. For the selector: draw one row per entry, keep an index, move it on
|
|
501
|
+
joystick up and down edges, act on fire. Reprint the old row plain
|
|
502
|
+
and the new row in reverse video rather than redrawing the table.
|
|
503
|
+
|
|
504
|
+
```asm
|
|
505
|
+
// directory_read_and_select: stream "$" through CHRIN into a table of
|
|
506
|
+
// name, block count and type, one entry per line, no buffer for the listing
|
|
507
|
+
.const SETLFS = $ffba
|
|
508
|
+
.const SETNAM = $ffbd
|
|
509
|
+
.const OPEN = $ffc0
|
|
510
|
+
.const CLOSE = $ffc3
|
|
511
|
+
.const CHKIN = $ffc6
|
|
512
|
+
.const CLRCHN = $ffcc
|
|
513
|
+
.const CHRIN = $ffcf
|
|
514
|
+
.const READST = $ffb7
|
|
515
|
+
.const MAXENT = 16 // entries the table holds
|
|
516
|
+
.const ENTLEN = 20 // 16 name, 1 zero, 2 blocks, 1 type
|
|
517
|
+
.const ptr = $fb // zero page pair for (ptr),y
|
|
518
|
+
|
|
519
|
+
* = $c000
|
|
520
|
+
read_dir:
|
|
521
|
+
lda #0
|
|
522
|
+
sta count
|
|
523
|
+
sta line
|
|
524
|
+
sta status
|
|
525
|
+
lda #2
|
|
526
|
+
ldx #8
|
|
527
|
+
ldy #0 // secondary 0: the load channel
|
|
528
|
+
jsr SETLFS
|
|
529
|
+
lda #1
|
|
530
|
+
ldx #<dollar
|
|
531
|
+
ldy #>dollar
|
|
532
|
+
jsr SETNAM
|
|
533
|
+
jsr OPEN
|
|
534
|
+
bcc opened
|
|
535
|
+
jmp dir_failed
|
|
536
|
+
opened:
|
|
537
|
+
ldx #2
|
|
538
|
+
jsr CHKIN
|
|
539
|
+
bcc reading
|
|
540
|
+
jmp dir_failed
|
|
541
|
+
reading:
|
|
542
|
+
jsr get // load address, two bytes, unused
|
|
543
|
+
jsr get
|
|
544
|
+
next_line:
|
|
545
|
+
jsr get // link low
|
|
546
|
+
sta tmp
|
|
547
|
+
jsr get // link high
|
|
548
|
+
ora tmp
|
|
549
|
+
beq dir_done // a zero link ends the listing
|
|
550
|
+
jsr get // line number = blocks (or free count)
|
|
551
|
+
sta blocks
|
|
552
|
+
jsr get
|
|
553
|
+
sta blocks+1
|
|
554
|
+
lda line
|
|
555
|
+
beq skip_text // line 0 is the disk header
|
|
556
|
+
ldy #0
|
|
557
|
+
find_quote:
|
|
558
|
+
jsr get
|
|
559
|
+
beq end_of_line // no quote at all: BLOCKS FREE line
|
|
560
|
+
cmp #$22
|
|
561
|
+
bne find_quote
|
|
562
|
+
ldx count
|
|
563
|
+
cpx #MAXENT
|
|
564
|
+
bcs skip_text // table full: drain the rest
|
|
565
|
+
jsr entry_ptr // ptr = table + count*ENTLEN
|
|
566
|
+
name_char:
|
|
567
|
+
jsr get
|
|
568
|
+
beq end_of_line
|
|
569
|
+
cmp #$22
|
|
570
|
+
beq name_done
|
|
571
|
+
sta (ptr),y
|
|
572
|
+
iny
|
|
573
|
+
cpy #16
|
|
574
|
+
bne name_char
|
|
575
|
+
jsr get // a full 16-byte name: swallow its closing quote
|
|
576
|
+
name_done:
|
|
577
|
+
lda #0
|
|
578
|
+
sta (ptr),y // terminate the name
|
|
579
|
+
ldy #17
|
|
580
|
+
lda blocks
|
|
581
|
+
sta (ptr),y
|
|
582
|
+
lda blocks+1
|
|
583
|
+
iny
|
|
584
|
+
sta (ptr),y
|
|
585
|
+
type_char:
|
|
586
|
+
jsr get // spaces, maybe '*', then P/S/R/U/D
|
|
587
|
+
beq end_of_line
|
|
588
|
+
cmp #' '
|
|
589
|
+
beq type_char
|
|
590
|
+
cmp #'*'
|
|
591
|
+
beq type_char // a splat: the type letter follows
|
|
592
|
+
ldy #19
|
|
593
|
+
sta (ptr),y // first letter of the type
|
|
594
|
+
inc count
|
|
595
|
+
skip_text:
|
|
596
|
+
jsr get // drain to the line's zero
|
|
597
|
+
bne skip_text
|
|
598
|
+
end_of_line:
|
|
599
|
+
inc line
|
|
600
|
+
lda status
|
|
601
|
+
beq next_line // ST = 0: more bytes follow
|
|
602
|
+
dir_done:
|
|
603
|
+
lda blocks
|
|
604
|
+
sta free
|
|
605
|
+
lda blocks+1
|
|
606
|
+
sta free+1 // the last line number is BLOCKS FREE
|
|
607
|
+
jsr CLRCHN
|
|
608
|
+
lda #2
|
|
609
|
+
jsr CLOSE
|
|
610
|
+
clc
|
|
611
|
+
rts
|
|
612
|
+
dir_failed:
|
|
613
|
+
jsr CLRCHN
|
|
614
|
+
lda #2
|
|
615
|
+
jsr CLOSE
|
|
616
|
+
sec
|
|
617
|
+
rts
|
|
618
|
+
|
|
619
|
+
// one byte from the channel; Z reflects the byte, status holds ST
|
|
620
|
+
get:
|
|
621
|
+
jsr CHRIN
|
|
622
|
+
sta byte
|
|
623
|
+
jsr READST
|
|
624
|
+
sta status
|
|
625
|
+
lda byte
|
|
626
|
+
rts
|
|
627
|
+
|
|
628
|
+
entry_ptr:
|
|
629
|
+
lda #<table
|
|
630
|
+
sta ptr
|
|
631
|
+
lda #>table
|
|
632
|
+
sta ptr+1
|
|
633
|
+
txa
|
|
634
|
+
beq ptr_done
|
|
635
|
+
mul:
|
|
636
|
+
lda ptr
|
|
637
|
+
clc
|
|
638
|
+
adc #ENTLEN
|
|
639
|
+
sta ptr
|
|
640
|
+
bcc no_carry
|
|
641
|
+
inc ptr+1
|
|
642
|
+
no_carry:
|
|
643
|
+
dex
|
|
644
|
+
bne mul
|
|
645
|
+
ptr_done:
|
|
646
|
+
rts
|
|
647
|
+
|
|
648
|
+
.encoding "petscii_upper"
|
|
649
|
+
dollar: .text "$"
|
|
650
|
+
count: .byte 0
|
|
651
|
+
line: .byte 0
|
|
652
|
+
status: .byte 0
|
|
653
|
+
byte: .byte 0
|
|
654
|
+
tmp: .byte 0
|
|
655
|
+
blocks: .word 0
|
|
656
|
+
free: .word 0
|
|
657
|
+
table: .fill MAXENT*ENTLEN, 0
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Assembled and run here from BASIC (`LOAD "DIRTECH",8,1` then
|
|
661
|
+
`SYS 49152`) against a disk holding three files and itself: `count`
|
|
662
|
+
came back 4, `status` `$40` and `free` 655, the figures `c1541 -list`
|
|
663
|
+
printed for the same disk. A second run against a disk holding a
|
|
664
|
+
300-byte SEQ file with a sixteen-character name and the routine itself
|
|
665
|
+
read the table back with PEEK: `count` 2, `status` `$40`, the first
|
|
666
|
+
entry's name bytes `A` to `P` with a zero after them, its block count 2
|
|
667
|
+
and its type letter `S`, the second entry's type `P`. An earlier draft
|
|
668
|
+
of the name loop left the closing quote unread after a full
|
|
669
|
+
sixteen-byte name and stored it as the type; the `jsr get` after the
|
|
670
|
+
loop is what that run checks.
|
|
671
|
+
|
|
672
|
+
### Why it works
|
|
673
|
+
|
|
674
|
+
The 1541 DOS recognises `$` at OPEN and builds the listing block by
|
|
675
|
+
block from the directory sectors on track 18, sending it on the data
|
|
676
|
+
channel with EOI on the last byte, which is why READST reads `$40`
|
|
677
|
+
there and nowhere earlier. The image is a BASIC program so that
|
|
678
|
+
`LOAD "$",8` followed by `LIST` shows it without any code on the C64
|
|
679
|
+
side. That convenience is the whole reason for the load address, the
|
|
680
|
+
link bytes and the line numbers, and a parser that reads it as a file
|
|
681
|
+
just has to know the shape. The block count sits in the line number
|
|
682
|
+
because BASIC prints line numbers in decimal for free; the DOS never
|
|
683
|
+
writes it into the text. Each entry costs 32 bytes on the wire (link,
|
|
684
|
+
number, up to 27 bytes of text, zero), so a directory of `n` files is
|
|
685
|
+
about `32 * (n + 2)` bytes, 160 for three files (measured, the recipe
|
|
686
|
+
below).
|
|
687
|
+
|
|
688
|
+
### Variations
|
|
689
|
+
|
|
690
|
+
- **More entries than the screen.** A 1541 directory holds up to 144
|
|
691
|
+
entries and a 25-row screen shows about 22. Keep the whole table
|
|
692
|
+
(144 entries of 20 bytes is under 3 KB) and draw a window of it,
|
|
693
|
+
moving the window when the index leaves it. The parse is one pass
|
|
694
|
+
regardless.
|
|
695
|
+
- **Only PRG files.** Test the first type letter and skip an entry
|
|
696
|
+
whose type is not `P`; the table stays small and the menu shows only
|
|
697
|
+
what the loader can use. Skip splat files too, or mark them.
|
|
698
|
+
- **Filtered by the drive.** `$:NAME*` or `$:*=P` asks the DOS to
|
|
699
|
+
filter by pattern or type before sending, so the C64 receives only
|
|
700
|
+
the matching lines. Not measured here.
|
|
701
|
+
- **The LOAD "$" trap from BASIC.** A BASIC program that wants a
|
|
702
|
+
directory opens `$` with `OPEN 2,8,0,"$"` and reads it with `GET#2`,
|
|
703
|
+
skipping the link and number bytes as above; it never uses LOAD for
|
|
704
|
+
it. The DOS wedge's `@$` does the same read.
|
|
705
|
+
- **Streaming or buffered.** The listing above parses straight from
|
|
706
|
+
CHRIN and needs no buffer. The recipe buffers the stream first so the
|
|
707
|
+
parse can be timed apart from the transfer and the channel is closed
|
|
708
|
+
before any screen output; either shape gives the same table.
|
|
709
|
+
|
|
710
|
+
### Cycle budget
|
|
711
|
+
|
|
712
|
+
Measured in VICE 3.10 x64sc with a true-drive 1541 and wobble off, on
|
|
713
|
+
the recipe below, CIA2 timers chained: the transfer of a 160-byte
|
|
714
|
+
listing (OPEN, CHKIN, 160 CHRIN and READST pairs, CLRCHN, CLOSE) took
|
|
715
|
+
515,635 cycles on PAL and 535,744 on NTSC, about 3,200 cycles a byte.
|
|
716
|
+
The parse of the buffered 160 bytes took 13,821 cycles on PAL and
|
|
717
|
+
14,251 on NTSC, about 86 cycles a byte. Both are one-off costs paid
|
|
718
|
+
when the menu is built, not per frame, so the Cost line carries no
|
|
719
|
+
`cycles_per_frame`; both timings read the chained counter unlatched,
|
|
720
|
+
so each is good to within a few hundred cycles rather than to the
|
|
721
|
+
cycle. The Cost line's `bytes_data` is the recipe's own data: the
|
|
722
|
+
1,024-byte listing buffer, the 411-byte table, the 40-byte reply
|
|
723
|
+
buffer and the 64-byte write block; the linker map's data and BSS
|
|
724
|
+
segments together come to 1,620 bytes with the runtime's own. By
|
|
725
|
+
arithmetic from those rates, a full 144-entry directory of about
|
|
726
|
+
4,700 bytes would take some 15 million cycles to transfer and 400,000
|
|
727
|
+
to parse. The transfer runs with interrupts off inside the KERNAL, so
|
|
728
|
+
a raster IRQ misses most frames during it
|
|
729
|
+
(`raster_irq_during_serial_io`).
|
|
730
|
+
|
|
731
|
+
### Recipes
|
|
732
|
+
|
|
733
|
+
- `recipes/oscar64/directory-reader.md` (three files written, the table parsed and checked, a joystick selector with an autopilot pick; transfer and parse timed)
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## kernal_load_to_address — LOAD a raw asset to an address of your choosing
|
|
738
|
+
|
|
739
|
+
**Complexity:** low
|
|
740
|
+
**Region:** both
|
|
741
|
+
**Uses registers:** (none)
|
|
742
|
+
**Uses kernal:** SETLFS, SETNAM, LOAD, SETMSG
|
|
743
|
+
|
|
744
|
+
### Why
|
|
745
|
+
|
|
746
|
+
Level data, a character set or a music file arrives as a PRG on the
|
|
747
|
+
disk: two bytes of load address, then the bytes. Sometimes the program
|
|
748
|
+
wants it where the file says; sometimes it wants it somewhere else, for
|
|
749
|
+
example the same tile file into whichever of two buffers is free. LOAD
|
|
750
|
+
does both, and which one is chosen by the secondary address given to
|
|
751
|
+
SETLFS, not by a LOAD argument.
|
|
752
|
+
|
|
753
|
+
### How
|
|
754
|
+
|
|
755
|
+
1. SETLFS with any logical file number, X = 8, and Y = 0 or 1. With
|
|
756
|
+
Y = 0 the address you pass in X/Y to LOAD is used and the file's own
|
|
757
|
+
header is discarded. With Y = 1 the header decides and LOAD's X/Y are
|
|
758
|
+
ignored.
|
|
759
|
+
2. SETNAM with the bare name. No `,P,R` is needed: the drive assumes PRG
|
|
760
|
+
for a load.
|
|
761
|
+
3. A = 0 for load (1 verifies against memory instead). X/Y = the
|
|
762
|
+
destination when the secondary address is 0.
|
|
763
|
+
4. JSR LOAD. On return C=0 and X/Y hold the address one past the last
|
|
764
|
+
byte written, which is how to learn the file's length. C=1 with A=4
|
|
765
|
+
is file not found, 5 no device, and A=0 means RUN/STOP was pressed.
|
|
766
|
+
|
|
767
|
+
```asm
|
|
768
|
+
// kernal_load_to_address: pull a raw asset in at an address of our choosing
|
|
769
|
+
.const SETLFS = $ffba
|
|
770
|
+
.const SETNAM = $ffbd
|
|
771
|
+
.const LOAD = $ffd5
|
|
772
|
+
.const SETMSG = $ff90
|
|
773
|
+
|
|
774
|
+
* = $c000
|
|
775
|
+
load_tiles:
|
|
776
|
+
lda #0
|
|
777
|
+
jsr SETMSG // no SEARCHING FOR / LOADING on screen
|
|
778
|
+
lda #1 // logical file number: LOAD ignores it
|
|
779
|
+
ldx #8
|
|
780
|
+
ldy #0 // secondary 0: OUR address wins
|
|
781
|
+
jsr SETLFS
|
|
782
|
+
lda #tname_end-tname
|
|
783
|
+
ldx #<tname
|
|
784
|
+
ldy #>tname
|
|
785
|
+
jsr SETNAM
|
|
786
|
+
lda #0 // 0 = load, 1 = verify
|
|
787
|
+
ldx #<$6000 // destination, used only with secondary 0
|
|
788
|
+
ldy #>$6000
|
|
789
|
+
jsr LOAD
|
|
790
|
+
bcs load_failed // C=1: A=4 file not found, 5 no device,
|
|
791
|
+
stx end_lo // 0 RUN/STOP; X/Y = end address + 1
|
|
792
|
+
sty end_hi
|
|
793
|
+
clc
|
|
794
|
+
rts
|
|
795
|
+
load_failed:
|
|
796
|
+
sec
|
|
797
|
+
rts
|
|
798
|
+
|
|
799
|
+
load_where_saved:
|
|
800
|
+
lda #1
|
|
801
|
+
ldx #8
|
|
802
|
+
ldy #1 // secondary 1: the file's own header wins
|
|
803
|
+
jsr SETLFS
|
|
804
|
+
lda #tname_end-tname
|
|
805
|
+
ldx #<tname
|
|
806
|
+
ldy #>tname
|
|
807
|
+
jsr SETNAM
|
|
808
|
+
lda #0
|
|
809
|
+
jsr LOAD // X/Y are ignored here
|
|
810
|
+
rts
|
|
811
|
+
|
|
812
|
+
.encoding "petscii_upper"
|
|
813
|
+
tname: .text "TILES" // a PRG: two-byte load address, then data
|
|
814
|
+
tname_end:
|
|
815
|
+
end_lo: .byte 0
|
|
816
|
+
end_hi: .byte 0
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Measured (VICE 3.10, 1541 true drive): a 16-byte PRG saved from `$5000`
|
|
820
|
+
loaded with secondary 0 and X/Y = `$6000` returned C=0 with X/Y =
|
|
821
|
+
`$6010` and the 16 bytes at `$6000`; the same file loaded with secondary
|
|
822
|
+
1 and X/Y set to `$1234` returned X/Y = `$5010` with the bytes back at
|
|
823
|
+
`$5000`; a name not on the disk returned C=1, A=`$04` and the error
|
|
824
|
+
channel then read `62, FILE NOT FOUND,00,00`.
|
|
825
|
+
|
|
826
|
+
### What the KERNAL prints
|
|
827
|
+
|
|
828
|
+
LOAD writes `SEARCHING FOR name` and `LOADING` to the screen only when
|
|
829
|
+
bit 7 of the message flag at `$9D` is set. BASIC sets that bit in direct
|
|
830
|
+
mode and clears it while a program runs, so a LOAD called from a program
|
|
831
|
+
started with RUN or a `SYS` line prints nothing: `$9D` read `$00` at
|
|
832
|
+
entry to the test program and the first LOAD left the screen alone
|
|
833
|
+
(measured). After `SETMSG` with A = `$80` the same LOAD printed a blank
|
|
834
|
+
line, `SEARCHING FOR BLOB` on the next, and `LOADING` with no carriage
|
|
835
|
+
return after it, so the program's next CHROUT landed on the same line
|
|
836
|
+
as `LOADING` (measured). Call SETMSG with A = 0 before loading from a game screen, or
|
|
837
|
+
save and restore `$9D` around the call if a BASIC front end expects it.
|
|
838
|
+
|
|
839
|
+
### Why it works
|
|
840
|
+
|
|
841
|
+
The serial LOAD reads the two header bytes first. With secondary 0 the
|
|
842
|
+
KERNAL substitutes the caller's X/Y; with secondary 1 it keeps them. Either way it then stores every following byte through
|
|
843
|
+
the zero-page pointer `$AE/$AF`, which is why a load lands in RAM under
|
|
844
|
+
BASIC or KERNAL ROM but into the chip registers at `$D000-$DFFF`, both
|
|
845
|
+
measured in `hardware/kernal-routines-reference.md` under LOAD. The
|
|
846
|
+
end address returned in X/Y is that pointer after the last store.
|
|
847
|
+
|
|
848
|
+
### Variations
|
|
849
|
+
|
|
850
|
+
- **Verify.** A = 1 compares the file against memory and sets bit 4 of
|
|
851
|
+
ST on a mismatch; the memory is not written.
|
|
852
|
+
- **Load into colour RAM.** Secondary 0 with X/Y = `$D800` writes the
|
|
853
|
+
colour nybbles straight in; the reference's LOAD entry shows why this
|
|
854
|
+
works and why `$D000-$DFFF` in general is a trap.
|
|
855
|
+
- **Chained loads under an IRQ.** LOAD, like OPEN and the channel
|
|
856
|
+
calls, does not respect a caller's SEI; the `kernal_assumes_sei_cleared`
|
|
857
|
+
pitfall covers what that does to a raster IRQ. Measured on LOAD
|
|
858
|
+
itself in `recipes/oscar64/load-asset-runtime.md`: entered with the
|
|
859
|
+
I flag set, LOAD returned with it clear, and a `rasterirq.h` split
|
|
860
|
+
armed across a 2 KB load entered in 222 of 283 PAL frames, as late
|
|
861
|
+
as line 170; the screen was not blanked (`$D011` bit 4 still set,
|
|
862
|
+
text drawn in a mid-transfer picture). `raster_irq_during_serial_io`
|
|
863
|
+
has the fix, which is to clear `$D01A` around the call.
|
|
864
|
+
- **From Oscar64.** `krnio_load(fnum, device, channel)` in
|
|
865
|
+
`kernalio.c` passes X = Y = 0 to LOAD, so with secondary 0 it loads
|
|
866
|
+
to `$0000`; it is only useful with secondary 1. To choose the address
|
|
867
|
+
from C, call SETLFS, SETNAM and LOAD from inline assembly as the
|
|
868
|
+
recipe below does. `krnio_save(device, start, end)` writes the
|
|
869
|
+
header from `start`, so a file saved from a buffer LOADs back to that
|
|
870
|
+
buffer with secondary 1 and anywhere with secondary 0.
|
|
871
|
+
|
|
872
|
+
### Recipes
|
|
873
|
+
|
|
874
|
+
- `recipes/kickassembler/file-io-roundtrip.md` (write and read side;
|
|
875
|
+
the LOAD measurements above came from a scratch program that is not a
|
|
876
|
+
recipe)
|
|
877
|
+
- `recipes/oscar64/load-asset-runtime.md` (a 2 KB charset built in
|
|
878
|
+
RAM, saved as a PRG on the first run, loaded with secondary 0 to
|
|
879
|
+
`$3800` and shown; the I flag, the raster IRQ and `$D011` measured
|
|
880
|
+
across the LOAD)
|
|
881
|
+
|
|
882
|
+
---
|
|
883
|
+
|
|
884
|
+
## kernal_relative_file_io — Read and write one record of a relative file by number
|
|
885
|
+
|
|
886
|
+
**Complexity:** medium
|
|
887
|
+
**Region:** both
|
|
888
|
+
**Uses registers:** (none)
|
|
889
|
+
**Uses kernal:** SETLFS, SETNAM, OPEN, CHKOUT, CHROUT, CHKIN, CHRIN, READST, CLRCHN, CLOSE
|
|
890
|
+
|
|
891
|
+
### Why
|
|
892
|
+
|
|
893
|
+
A sequential file gives back its bytes from the start, every time. A
|
|
894
|
+
game with eight save slots, a level bank of sixty rooms or a table of
|
|
895
|
+
player names wants slot 5 or room 42 without streaming everything before
|
|
896
|
+
it, and wants to overwrite one entry without rewriting the file. The
|
|
897
|
+
1541's relative file (directory type `REL`) does that: fixed-length
|
|
898
|
+
records, addressed by number, positioned by a command on channel 15,
|
|
899
|
+
and the same KERNAL calls as a sequential file for the bytes themselves.
|
|
900
|
+
The drive does the seeking; the C64 side sends five bytes and reads a
|
|
901
|
+
reply.
|
|
902
|
+
|
|
903
|
+
### How
|
|
904
|
+
|
|
905
|
+
1. SETLFS with a logical file number, device 8 and a secondary address
|
|
906
|
+
from 2 to 14; SETNAM with the name followed by `,L,` and **one more
|
|
907
|
+
byte, the record length** (1 to 254). The length byte is binary, not
|
|
908
|
+
a digit, so the name is set by length and address rather than as a
|
|
909
|
+
text string. OPEN. On a fresh disk this creates the file; the drive
|
|
910
|
+
answers `00, OK` on channel 15 and the directory shows a `REL` entry.
|
|
911
|
+
2. Open the command channel (secondary 15, empty name) before the data
|
|
912
|
+
file or after it, and **keep it open until the data file is closed**:
|
|
913
|
+
CLOSE of secondary 15 makes the drive close every channel it has, the
|
|
914
|
+
REL file included.
|
|
915
|
+
3. To reach a record, send the P command on channel 15: CHKOUT 15, then
|
|
916
|
+
CHROUT of the five bytes `P`, the data channel's secondary address
|
|
917
|
+
plus 96, the record number low byte, the record number high byte,
|
|
918
|
+
and the byte offset inside the record, then CLRCHN. Records and
|
|
919
|
+
offsets count from 1. Then read the reply from channel 15 as
|
|
920
|
+
error_channel_check does: `00` means the record exists, `50, RECORD
|
|
921
|
+
NOT PRESENT` means it does not yet.
|
|
922
|
+
4. Read: CHKIN the data file, CHRIN and READST until EOF, CLRCHN. The
|
|
923
|
+
drive sends the record's bytes up to the last non-zero one and raises
|
|
924
|
+
EOI there, so a record padded with zeros comes back short.
|
|
925
|
+
5. Write: CHKOUT the data file, CHROUT up to the record length, CLRCHN.
|
|
926
|
+
The bytes go when the KERNAL sends EOI on the last one, and the
|
|
927
|
+
drive pads the rest of the record with zeros. More bytes than the
|
|
928
|
+
record length answer `51,OVERFLOW IN RECORD` on channel 15 and the
|
|
929
|
+
KERNAL status stays `$00`.
|
|
930
|
+
6. Send a P before every read and every write. The pointer moves as
|
|
931
|
+
bytes move, so a second read without a P starts on the next record.
|
|
932
|
+
7. CLOSE the data file, then CLOSE 15.
|
|
933
|
+
|
|
934
|
+
Measured in VICE x64sc 3.10 with the recipe below, a 32-byte record
|
|
935
|
+
length on a fresh disk (rung 1): after the OPEN that created the file
|
|
936
|
+
the disk was two blocks shorter, one data block and one side sector;
|
|
937
|
+
positioning on records 1 to 7 answered `00` before any of them was
|
|
938
|
+
written, and a read of an unwritten one returned a single `$FF` with
|
|
939
|
+
EOF; positioning on record 8, 9 or 20 answered `50, RECORD NOT
|
|
940
|
+
PRESENT,00,00`, and a read after that reply returned a single `$0D`
|
|
941
|
+
with EOF and left the status at `50`; a 32-byte write to record 9 after
|
|
942
|
+
the `50` answered `00, OK`, grew the file by one block, and the next P
|
|
943
|
+
to record 9 answered `00` with all 32 bytes reading back. Seven 32-byte
|
|
944
|
+
records are 224 bytes and an eighth would cross the 254 data bytes of a
|
|
945
|
+
block, so the boundary is the end of the first data block; that the
|
|
946
|
+
open allocates that block and marks each record in it with `$FF` is
|
|
947
|
+
inference from those two measurements (rung 3). The DOS also reports
|
|
948
|
+
`50` when a write extends the file, per the ROM's call sites in
|
|
949
|
+
`../formats/iec-disk-reference.md`; in these runs the write itself
|
|
950
|
+
answered `00` and only the P before it said `50`. `50` and `51` never
|
|
951
|
+
reached the KERNAL's status byte, which read `$00` after every write
|
|
952
|
+
and `$40` after every read; only channel 15 knows. A side run with a
|
|
953
|
+
scratch program, not the recipe, checked steps 3, 4 and 6 (rung 1): a
|
|
954
|
+
record written as ten letters and 22 zeros read back as ten bytes; a P
|
|
955
|
+
with offset 5 read six bytes from the fifth letter, and offset 0
|
|
956
|
+
behaved as 1; a read with no P after a full record returned a single
|
|
957
|
+
byte, the next record's.
|
|
958
|
+
|
|
959
|
+
### Why it works
|
|
960
|
+
|
|
961
|
+
The drive holds a record pointer per open relative file and a set of
|
|
962
|
+
side sectors, blocks that list the track and sector of every data block
|
|
963
|
+
in the file in order. A P command is arithmetic on the drive: record
|
|
964
|
+
number times record length gives a byte offset, the side sector turns
|
|
965
|
+
that into a data block, and the drive seeks straight to it. Extending
|
|
966
|
+
the file is where the cost lives: a write past the end allocates the
|
|
967
|
+
data blocks up to and including the new record, plus a side sector for
|
|
968
|
+
every 120 data blocks, and every new record starts with `$FF`. The
|
|
969
|
+
side sectors are why a REL costs one block more than the data on
|
|
970
|
+
creation and why creating a large file takes long. The figure of one
|
|
971
|
+
side sector per 120 data blocks was the format's stated rule when this
|
|
972
|
+
was first written; it has since been read off a disk image, a
|
|
973
|
+
one-byte write to record 125 of a 254-byte-record file allocating 125
|
|
974
|
+
data blocks and two side sectors of 120 and 5 entries
|
|
975
|
+
(`../formats/c64-file-formats.md`, "REL file", rung 1).
|
|
976
|
+
|
|
977
|
+
`c1541 -write` can create a REL entry but cannot lay out records. The
|
|
978
|
+
record length goes on the end of the name as one byte, so `c1541
|
|
979
|
+
-write blob.bin "blob,l, "` (the trailing space is byte 32) printed
|
|
980
|
+
`Open new REL file 'BLOB' with record length 32 on channel 1.`, and
|
|
981
|
+
the directory then showed `blob` as `rel` with 662 blocks free. But
|
|
982
|
+
c1541 streams the whole file as one record: a 64-byte blob drew `ERR =
|
|
983
|
+
51, OVERFLOW IN RECORD, 00, 00` once for each byte past the
|
|
984
|
+
thirty-second, so at most record 1 is filled. Without the length byte,
|
|
985
|
+
`c1541 -write blob.bin "blob,l"` answered `Open non-existing REL file
|
|
986
|
+
'BLOB' with unspecified record length on channel 1.` and `floppy write
|
|
987
|
+
failed`, and the directory gained no entry (both rung 1, c1541 3.10).
|
|
988
|
+
|
|
989
|
+
The KERNAL knows none of this. SETLFS, SETNAM, OPEN and the CHKIN,
|
|
990
|
+
CHKOUT, CHRIN, CHROUT, CLRCHN, CLOSE pairs behave exactly as in
|
|
991
|
+
kernal_file_write_seq and kernal_file_read_seq, and everything on this
|
|
992
|
+
page about interrupts, bus pace and the status byte applies: the calls
|
|
993
|
+
re-enable interrupts (`kernal_assumes_sei_cleared`), a raster IRQ armed
|
|
994
|
+
across them misses most frames (`raster_irq_during_serial_io`), and
|
|
995
|
+
they need the KERNAL ROM in (`kernal_io_mapping_dependency`). In
|
|
996
|
+
assembly, the P command is five CHROUT calls, and CHROUT and CHKOUT
|
|
997
|
+
keep none of A, X or Y, so a record number held in X or Y across them
|
|
998
|
+
is gone by the second byte (`kernal_clobbers_a_x_y`); keep it in
|
|
999
|
+
memory and load each byte fresh.
|
|
1000
|
+
|
|
1001
|
+
### When a REL beats a SEQ
|
|
1002
|
+
|
|
1003
|
+
- **Random access with rewrites in place.** Save slot 3 of 8, room 42
|
|
1004
|
+
of 60, the name at table entry 17: one P and one read or write, no
|
|
1005
|
+
rewrite of the neighbours. A SEQ needs the whole file read into RAM
|
|
1006
|
+
and written back.
|
|
1007
|
+
- **Not for streaming.** A level that is always read whole is a SEQ or
|
|
1008
|
+
a LOAD; the REL's side sector and its per-record P are a cost that
|
|
1009
|
+
gives nothing back.
|
|
1010
|
+
- **Not for assets shipped on the disk image.** `c1541 -write` can
|
|
1011
|
+
create the REL entry but fills at most record 1 (measured above), so
|
|
1012
|
+
the program should create and fill the REL on first run; a shipped
|
|
1013
|
+
table is a SEQ or PRG the program copies into a REL if it wants one.
|
|
1014
|
+
|
|
1015
|
+
### Variations
|
|
1016
|
+
|
|
1017
|
+
- **Record length 254 or less.** Choose a length that divides 254 with
|
|
1018
|
+
no remainder (2, 127, 254) or accept that records straddle blocks,
|
|
1019
|
+
which the drive handles and the program never sees.
|
|
1020
|
+
- **Position on a byte inside the record.** The fifth byte of the P
|
|
1021
|
+
command is a 1-based offset (measured above on a read); a P with
|
|
1022
|
+
offset 17 followed by a write of four bytes changes only bytes 17 to
|
|
1023
|
+
20 (the DOS rule; the write at an offset was not measured here).
|
|
1024
|
+
- **Pre-size the file.** Position on the highest record you will use
|
|
1025
|
+
and write one byte to it once, at first run, so later writes never
|
|
1026
|
+
extend the file mid-game and pay the block allocation then. The
|
|
1027
|
+
extension cost itself was not measured here.
|
|
1028
|
+
- **Ask before reading.** The `50` reply to a P is the cheap way to know
|
|
1029
|
+
a slot is empty: no read, no `$0D`, and no need to reserve a byte in
|
|
1030
|
+
the record as an "in use" flag.
|
|
1031
|
+
|
|
1032
|
+
### Recipes
|
|
1033
|
+
|
|
1034
|
+
- `recipes/oscar64/relative-file-records.md` (create, write 1, 3 and 5, read back with a checksum, the `50` on record 9 and the write that clears it; `51` in a side run)
|
|
1035
|
+
- `recipes/oscar64/rel-side-sectors.md` (100-byte records across four blocks, then the directory entry and the side sector read back through `U1`; the bytes are decoded in `../formats/c64-file-formats.md`, "REL file")
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
## cartridge_save — Save game data to the cartridge: EasyFlash flash sectors, GMod2 serial EEPROM
|
|
1040
|
+
|
|
1041
|
+
**Complexity:** medium
|
|
1042
|
+
**Region:** both
|
|
1043
|
+
**Uses registers:** DE00, DE02
|
|
1044
|
+
**Requires:** cartridge_bank_easyflash
|
|
1045
|
+
**Demands:** kernal_rom_out
|
|
1046
|
+
|
|
1047
|
+
### Why
|
|
1048
|
+
|
|
1049
|
+
A game shipped on a cartridge may run on a C64 with no disk drive. Even
|
|
1050
|
+
when there is one, the disk in it is not the game's. High scores and
|
|
1051
|
+
save games therefore go into the cartridge. Two cartridge types built for
|
|
1052
|
+
this are in common use. EasyFlash writes to its own flash chips.
|
|
1053
|
+
GMod2 writes to a small serial EEPROM beside its flash. The C64 cannot
|
|
1054
|
+
write to ROM, so both need their own write protocol, and both are slow
|
|
1055
|
+
compared with a RAM store. This technique describes both and when to
|
|
1056
|
+
pick which.
|
|
1057
|
+
|
|
1058
|
+
### How: EasyFlash
|
|
1059
|
+
|
|
1060
|
+
EasyFlash holds two 512 KB Am29F040-type flash chips, one behind ROML
|
|
1061
|
+
(`$8000`) and one behind ROMH. Each 8 KB bank register value
|
|
1062
|
+
(`$DE00`) selects the upper chip address lines. Three rules decide
|
|
1063
|
+
everything.
|
|
1064
|
+
|
|
1065
|
+
1. **Erase is per 64 KB sector, programming is per byte.** Erase sets a
|
|
1066
|
+
whole sector to `$FF`. That is 8 banks of one chip: the ROML halves of
|
|
1067
|
+
banks 8 to 15, for example. Programming can only turn 1 bits into 0
|
|
1068
|
+
bits. So reserve whole sectors for saves, never a sector that also
|
|
1069
|
+
holds code or data. The Am29F040B data sheet gives 1,000,000
|
|
1070
|
+
program/erase cycles per sector at minimum (not measured here).
|
|
1071
|
+
2. **Writes need Ultimax mode.** Reads work in 16 KB mode (`$DE02` =
|
|
1072
|
+
`$07`), but writes to ROML in that mode reach only the C64 RAM
|
|
1073
|
+
underneath. Measured in VICE x64sc 3.10: an erase issued in 16 KB mode
|
|
1074
|
+
returned in 130 cycles and changed nothing, and the RAM at `$8555`
|
|
1075
|
+
read back `$A0`, the program command's third byte. EAPI switches to
|
|
1076
|
+
`$85` (Ultimax plus LED) for every write. Write to ROML at
|
|
1077
|
+
`$8000-$9FFF` and to ROMH at `$E000-$FFFF`, the Ultimax addresses.
|
|
1078
|
+
In Ultimax mode only RAM `$0000-$0FFF` is mapped and the KERNAL is
|
|
1079
|
+
gone, so the writing code and its data live below `$1000` (or in the
|
|
1080
|
+
cartridge's 256 bytes of RAM at `$DF00`), with interrupts off.
|
|
1081
|
+
3. **Use EAPI in anything you release.** EAPI is the EasyFlash flash
|
|
1082
|
+
driver. A CRT carries it at bank 0 ROMH offset `$1800` (768 bytes
|
|
1083
|
+
reserved). The program copies it to C64 RAM (c64gameframework uses
|
|
1084
|
+
`$C000`) and calls EAPIInit, which builds a jump table in the
|
|
1085
|
+
cartridge RAM at `$DF80`. The calls are
|
|
1086
|
+
EAPIWriteFlash `$DF80`, EAPIEraseSector `$DF83`, EAPISetBank `$DF86`,
|
|
1087
|
+
EAPIGetBank `$DF89`, EAPISetPtr `$DF8C`, EAPISetLen `$DF8F`,
|
|
1088
|
+
EAPIReadFlashInc `$DF92` and EAPIWriteFlashInc `$DF95`. When EasyProg
|
|
1089
|
+
flashes a CRT that has the `eapi` signature there, it swaps in the
|
|
1090
|
+
version for the flash chip actually fitted. Code that sends Am29F040
|
|
1091
|
+
commands itself, as the recipe does, works only on that chip. VICE
|
|
1092
|
+
emulates it and warns `EF: EAPI not found!` when a CRT has no EAPI.
|
|
1093
|
+
|
|
1094
|
+
Saving then works like this. Keep the save sector's banks as `$FF` in
|
|
1095
|
+
the CRT, because EasyProg erases only the sectors a CRT contains. At
|
|
1096
|
+
boot, check a signature and erase the sector if it is foreign. Append
|
|
1097
|
+
each save as a fixed-size record in the next all-`$FF` slot, program a
|
|
1098
|
+
commit byte last, and load the newest committed record. Erase only when
|
|
1099
|
+
no slot is left. A save of a few dozen bytes then costs one sector erase
|
|
1100
|
+
every few hundred saves.
|
|
1101
|
+
|
|
1102
|
+
Times. VICE's sector erase took 1,000,147 to 1,000,191 cycles over three
|
|
1103
|
+
erases (two PAL, one NTSC). That is about 1 s: freeze the game and blank the screen, or show
|
|
1104
|
+
a "saving" message before starting. Programming 32 bytes took 2,866 to
|
|
1105
|
+
3,123 cycles in VICE (PAL and NTSC), including the code around each byte. The
|
|
1106
|
+
Am29F040B data sheet gives 1 s typical and 8 s maximum per sector erase,
|
|
1107
|
+
and 7 µs typical and 300 µs maximum per byte (not measured here). VICE
|
|
1108
|
+
does not model the byte time or its spread, so budget for the maximum
|
|
1109
|
+
on hardware.
|
|
1110
|
+
|
|
1111
|
+
The recipe's wait loops poll the toggle bit (DQ6) and do not check DQ5,
|
|
1112
|
+
which the data sheet sets when an operation exceeds its time limit; a
|
|
1113
|
+
released game should add that check or use EAPI (hardware failure not
|
|
1114
|
+
tested here). A power cut is still a risk in two places, and the recipe's
|
|
1115
|
+
torn-slot path was not exercised. (a) When the bank is full, the erase
|
|
1116
|
+
comes before the new record is written, so a cut there loses every save.
|
|
1117
|
+
(b) A cut during an erase can leave the signature and a `$00` commit
|
|
1118
|
+
byte over half-erased data, which goes undetected without a checksum.
|
|
1119
|
+
Remedies: alternate between two sectors, and add a checksum byte.
|
|
1120
|
+
|
|
1121
|
+
### How: GMod2
|
|
1122
|
+
|
|
1123
|
+
GMod2 keeps saves in a serial EEPROM behind `$DE00`. The same register
|
|
1124
|
+
also selects the ROM bank, so the code keeps a shadow copy and changes
|
|
1125
|
+
one bit at a time. The bits, from c64gameframework's `gmod2boot.s`:
|
|
1126
|
+
|
|
1127
|
+
| `$DE00` bit | Signal | Direction |
|
|
1128
|
+
|---|---|---|
|
|
1129
|
+
| 7 | EEPROM data out | read |
|
|
1130
|
+
| 6 | chip select | write |
|
|
1131
|
+
| 5 | clock | write |
|
|
1132
|
+
| 4 | EEPROM data in | write |
|
|
1133
|
+
|
|
1134
|
+
Each command is a start bit, a 2-bit opcode and a 10-bit word address,
|
|
1135
|
+
clocked out MSB first. Opcode `10` reads and `01` writes. `00` followed
|
|
1136
|
+
by `11` enables writes and `00` followed by `00` disables them. Data
|
|
1137
|
+
follows as 16-bit words: the framework halves its byte address to get
|
|
1138
|
+
the word address, and a write sends one word as two bytes. 1,024 16-bit
|
|
1139
|
+
words is 2 KB (arithmetic from the 10-bit address). After each word, the
|
|
1140
|
+
code drops and raises chip select, then waits until data out reads 1.
|
|
1141
|
+
The framework issues no erase command, so there is no sector to manage.
|
|
1142
|
+
It holds only 2 KB, though, and every bit costs several register writes. The part number and its
|
|
1143
|
+
write time come from neither the framework nor VICE and are not stated
|
|
1144
|
+
here. VICE emulates it with `-gmod2eepromimage <file>` and
|
|
1145
|
+
`-gmod2eepromrw`. Nothing on GMod2 was measured here.
|
|
1146
|
+
|
|
1147
|
+
### Which one
|
|
1148
|
+
|
|
1149
|
+
- **EasyFlash** if the game is on EasyFlash already, the save is large (a
|
|
1150
|
+
whole level state), or saves are rare enough that a 1 s erase is
|
|
1151
|
+
acceptable. Reserve a sector per save area.
|
|
1152
|
+
- **GMod2** for a 16-bit-word save of up to 2 KB that must be written
|
|
1153
|
+
often, with no erase planning. Its write time is not established here.
|
|
1154
|
+
- **Neither** for data that changes every frame: keep it in RAM and
|
|
1155
|
+
write it on checkpoint or game over.
|
|
1156
|
+
|
|
1157
|
+
### Sources
|
|
1158
|
+
|
|
1159
|
+
- EasyFlash Programmer's Guide (Thomas Giesel): http://skoe.de/easyflash/files/devdocs/EasyFlash-ProgRef.pdf
|
|
1160
|
+
- Am29F040B data sheet, AMD publication 21445: https://instrumentation.obs.carnegiescience.edu/ccd/parts/AM29F040B.pdf
|
|
1161
|
+
- c64gameframework (Lasse Öörni, MIT), `efboot.s`, `gmod2boot.s`, `eapi-am29f040-14.bin`: https://github.com/cadaver/c64gameframework
|
|
1162
|
+
|
|
1163
|
+
### Recipes
|
|
1164
|
+
|
|
1165
|
+
- `recipes/kickassembler/easyflash-save.md` (a self-built EasyFlash CRT that appends a high-score record to bank 8 each boot; persistence shown across two VICE runs with `-easyflashcrtwrite`; erase and program timed)
|
|
1166
|
+
|
|
1167
|
+
---
|
|
1168
|
+
|
|
1169
|
+
## drive_code_upload_and_job_queue — Upload code to the 1541 with M-W, start it with M-E, read sectors through its job queue
|
|
1170
|
+
|
|
1171
|
+
**Complexity:** medium
|
|
1172
|
+
**Region:** both
|
|
1173
|
+
**Uses registers:** (none)
|
|
1174
|
+
**Uses kernal:** SETLFS, SETNAM, OPEN, CHKOUT, CHROUT, CHKIN, CHRIN, CLRCHN, CLOSE
|
|
1175
|
+
**Requires:** error_channel_check
|
|
1176
|
+
**Cost:** bytes_code=865, bytes_data=341
|
|
1177
|
+
**Cost basis:** derived-listing
|
|
1178
|
+
**Cost measured on:** kickassembler-drive-job-queue (whole PRG less the BASIC stub: 837 bytes of host code and the 28-byte drive routine; the 40-byte ramp, the strings and the 128-byte read buffer are data)
|
|
1179
|
+
|
|
1180
|
+
### Why
|
|
1181
|
+
|
|
1182
|
+
The DOS reads what its file system describes. A loader that wants a
|
|
1183
|
+
sector by track and sector number, a protection check that wants to
|
|
1184
|
+
look at a sector the directory does not point to, or a fast loader that
|
|
1185
|
+
wants the drive's CPU running its own transfer loop, all need code on
|
|
1186
|
+
the drive side. The 1541 has a 6502 of its own, 2 KiB of RAM, and a
|
|
1187
|
+
command channel that will write that RAM, read it back and jump into
|
|
1188
|
+
it. The drive's disk controller then does the reading: uploaded code
|
|
1189
|
+
never has to touch the head or decode GCR to fetch a sector, it asks
|
|
1190
|
+
through the job queue.
|
|
1191
|
+
|
|
1192
|
+
### How
|
|
1193
|
+
|
|
1194
|
+
1. Open channel 15 bare (SETLFS 15, 8, 15; SETNAM length 0; OPEN), as
|
|
1195
|
+
error_channel_check does. It stays open for the whole exchange.
|
|
1196
|
+
2. Upload. Each `M-W` is a command on channel 15: CHKOUT 15, then the
|
|
1197
|
+
bytes `M`, `-`, `W`, address low, address high, count, and `count`
|
|
1198
|
+
data bytes, then CLRCHN. The UNLISTEN runs it. Send at most 32 data
|
|
1199
|
+
bytes per command (the DOS parses the command from a fixed buffer
|
|
1200
|
+
and 32 is the figure loaders use; the recipe sends 32, 32 and 4; a
|
|
1201
|
+
34-byte `M-W` also uploaded and ran in VICE, measured under
|
|
1202
|
+
`pitfalls/loader.md#atn_assert_drives_data_low_via_atna`, and 35 or
|
|
1203
|
+
more was not tried). Advance the address by the count each time.
|
|
1204
|
+
3. Verify with `M-R`: the six bytes `M`, `-`, `R`, low, high, count,
|
|
1205
|
+
then CHKIN 15 and `count` CHRIN calls, then CLRCHN. Compare with the
|
|
1206
|
+
source. The recipe reads 68 bytes in one command; larger counts were
|
|
1207
|
+
not measured here. The DOS takes the count from the sixth byte of
|
|
1208
|
+
the command.
|
|
1209
|
+
4. Start with `M-E`: the five bytes `M`, `-`, `E`, low, high. The drive
|
|
1210
|
+
executes a JSR to that address from its command parser, with
|
|
1211
|
+
interrupts enabled, and returns to its idle loop on the routine's
|
|
1212
|
+
RTS. The host's next command is not accepted until then, so a host
|
|
1213
|
+
that sends `M-E` and then `M-R` simply waits.
|
|
1214
|
+
5. Inside the routine, to read a sector: put the track in `$08` and the
|
|
1215
|
+
sector in `$09`, write `$80` to `$01`, and loop while `$01` has bit 7
|
|
1216
|
+
set. The controller, which runs from the drive's timer interrupt,
|
|
1217
|
+
replaces the job code with a result: `$01` is success and the data
|
|
1218
|
+
is in buffer 1 at `$0400`. Slot `$00` uses buffer 0 at `$0300` with
|
|
1219
|
+
its header at `$06`/`$07`; slot 2 buffer 2 at `$0500` with `$0A`/`$0B`;
|
|
1220
|
+
slot 3 buffer 3 at `$0600` with `$0C`/`$0D`; slot 4 buffer 4 at
|
|
1221
|
+
`$0700` with `$0E`/`$0F`; slot 5 has no RAM behind it. Job codes:
|
|
1222
|
+
`$80` read, `$90` write, `$A0` verify, `$B0` seek, `$C0` bump,
|
|
1223
|
+
`$D0` jump, `$E0` execute. The codes as run here were `$80` and
|
|
1224
|
+
`$B0`; the rest are from the ROM disassembly's list and were not run
|
|
1225
|
+
(see `../formats/iec-disk-reference.md`, "1541 job queue and
|
|
1226
|
+
buffers").
|
|
1227
|
+
6. Before the first read, teach the controller the disk ID. It checks
|
|
1228
|
+
every header's two ID bytes against its master copy at `$12`/`$13`,
|
|
1229
|
+
and after power-on nothing has set those, so the first read job
|
|
1230
|
+
fails with `$0B` (measured). A seek job, `$B0` with the track in
|
|
1231
|
+
`$08`, reads any header and copies its ID there (measured: `$01`,
|
|
1232
|
+
and `$12`/`$13` then read `30 31` for a `TEST,01` disk). The DOS's
|
|
1233
|
+
own `I0` does the same seek.
|
|
1234
|
+
7. Fetch the result and the data with `M-R` from the host: `M-R $0001`
|
|
1235
|
+
for the job byte, `M-R $0400` in chunks for the sector.
|
|
1236
|
+
|
|
1237
|
+
### Why it works
|
|
1238
|
+
|
|
1239
|
+
The drive's 6502 spends its time in two roles. From its idle loop it
|
|
1240
|
+
parses commands and runs the file system; from its timer interrupt,
|
|
1241
|
+
every ten milliseconds by the ROM disassembly's account (rung 4), it
|
|
1242
|
+
becomes the disk controller, scans `$00` to `$05` for a byte with bit 7
|
|
1243
|
+
set, and does that job with the head. `M-W` and `M-R` are ordinary
|
|
1244
|
+
commands that read and write the drive's address space; `M-E` is a
|
|
1245
|
+
command whose action is a subroutine call. Code started that way runs
|
|
1246
|
+
in the file-system role, so it can post jobs for the controller role
|
|
1247
|
+
exactly as the DOS does, and the result comes back in the same byte.
|
|
1248
|
+
The status line is not involved: after the track-40 job failed with
|
|
1249
|
+
`$03` the error channel still read `00, OK,00,00` (measured), because
|
|
1250
|
+
only the DOS writes that line and the DOS did not run the job.
|
|
1251
|
+
|
|
1252
|
+
Buffer choice: the DOS lends buffers 0 to 3 to data channels and keeps
|
|
1253
|
+
the BAM in buffer 4 at `$0700` (the ROM disassembly's note, rung 4; the
|
|
1254
|
+
"70, NO CHANNEL" on a fifth `#` open is the four in use). With no files
|
|
1255
|
+
open, `$0300` to `$06FF` is free. The recipe puts its code in buffer 3
|
|
1256
|
+
and reads into buffer 1; the code survived the four jobs and a seek.
|
|
1257
|
+
|
|
1258
|
+
### Variations
|
|
1259
|
+
|
|
1260
|
+
- **A resident drive program.** Upload once, `M-E` once, and never
|
|
1261
|
+
return: the routine takes over the bus with its own protocol over
|
|
1262
|
+
CLK and DATA, the host side drives it with a matching routine, and
|
|
1263
|
+
the KERNAL is out of the loop until the drive is reset. That is a
|
|
1264
|
+
fast loader, and the pitfalls are its own: `../pitfalls/loader.md`,
|
|
1265
|
+
`gcr_timing_assumes_stock_drive` (an SD2IEC has no CPU to run the
|
|
1266
|
+
upload, and a 1571 or 1581 has a different ROM and different
|
|
1267
|
+
addresses), `fastloader_resident_in_kernal_workspace` and
|
|
1268
|
+
`fastloader_dd00_write_corrupts_resident` for the host half. Not
|
|
1269
|
+
measured here.
|
|
1270
|
+
- **The 1571 and 1581.** Both accept `M-W`, `M-R` and `M-E` (rung 4), but
|
|
1271
|
+
their job queues, buffer addresses and controller codes are their own
|
|
1272
|
+
ROMs' and were not measured here; detect the drive first
|
|
1273
|
+
(`../formats/iec-disk-reference.md`, "Identifying the drive over the
|
|
1274
|
+
command channel").
|
|
1275
|
+
- **Writing.** Fill the buffer, post `$90`. Not run here, and it would
|
|
1276
|
+
change the disk, so it does not belong in the recipe's pinned run.
|
|
1277
|
+
- **Another buffer.** Point the read at buffer 2 (`$0500`) by posting the
|
|
1278
|
+
job in slot `$02` with the track and sector at `$0A`/`$0B`: the same
|
|
1279
|
+
code, one slot along. Not run here.
|
|
1280
|
+
|
|
1281
|
+
### Cycle budget
|
|
1282
|
+
|
|
1283
|
+
Bus time, PAL, VICE 3.10 (rung 1): 132,742 host cycles for the three
|
|
1284
|
+
`M-W` commands carrying 68 bytes, and 163,545 for the single `M-R` that
|
|
1285
|
+
read them back, about 135 ms and 166 ms. On the drive side, in drive
|
|
1286
|
+
cycles at 1 MHz: the seek 125,859, the successful read 224,071, the
|
|
1287
|
+
failed read before the seek 902,630 and the track-40 failure 766,533;
|
|
1288
|
+
the failures are the controller's retries and bumps. A loader that
|
|
1289
|
+
uploads a few hundred bytes therefore spends a noticeable fraction of a
|
|
1290
|
+
second on the upload alone, which is why resident loaders upload once.
|
|
1291
|
+
|
|
1292
|
+
### Recipes
|
|
1293
|
+
|
|
1294
|
+
- `recipes/kickassembler/drive-job-queue.md` (upload, readback, execute, a failed read, a seek, the BAM read and compared, a provoked `$03`, and what the error channel says afterwards; drive-side monitor trace of each job)
|
|
1295
|
+
|
|
1296
|
+
---
|
|
1297
|
+
|
|
1298
|
+
## tape_turbo_loader — Read a one-pulse-per-bit tape block by timing FLAG edges against a threshold
|
|
1299
|
+
|
|
1300
|
+
**Complexity:** medium
|
|
1301
|
+
**Region:** both
|
|
1302
|
+
**Uses registers:** DC06, DC07, DC0D, DC0F
|
|
1303
|
+
**Uses kernal:** (none)
|
|
1304
|
+
**Cost:** bytes_code=1060, bytes_data=110, zp_bytes=24
|
|
1305
|
+
**Cost basis:** derived-listing
|
|
1306
|
+
**Cost measured on:** kickassembler-tape-turbo-loader (whole PRG less the BASIC stub; the code figure includes the report and the 32-bit division, the data is the strings and counters)
|
|
1307
|
+
|
|
1308
|
+
### Why
|
|
1309
|
+
|
|
1310
|
+
The KERNAL's tape format spends twenty pulses on a byte, draws them from
|
|
1311
|
+
three lengths, and writes every block twice; measured from a SAVE in
|
|
1312
|
+
VICE (`../formats/c64-file-formats.md`, "KERNAL bit encoding") a byte
|
|
1313
|
+
costs 9,448 cycles, about 104 bytes a second for one copy and half that
|
|
1314
|
+
for the pair the KERNAL actually writes. A turbo loader replaces the
|
|
1315
|
+
stream, not the reading of it: one pulse per bit, two lengths, eight
|
|
1316
|
+
pulses to a byte, one copy. The recipe below moves 321 bytes a second on
|
|
1317
|
+
PAL and 334 on NTSC (measured, VICE 3.10), three times the KERNAL's one
|
|
1318
|
+
copy and six times its pair, with generous pulses; the trade is that the
|
|
1319
|
+
loader now depends on the tape running at the speed the file was
|
|
1320
|
+
mastered for, and on a threshold that sits between the two lengths.
|
|
1321
|
+
|
|
1322
|
+
### How
|
|
1323
|
+
|
|
1324
|
+
1. Master the tape with one pulse per bit. The recipe's script writes a
|
|
1325
|
+
TAP file: a lead-in of 1 bits, a sync byte `$5A`, a two-byte length,
|
|
1326
|
+
the data and an XOR checksum, most significant bit first, a 0 as a
|
|
1327
|
+
256-cycle pulse and a 1 as a 512-cycle pulse. TAP entries are cycles
|
|
1328
|
+
divided by eight, so the two lengths are `$20` and `$40`.
|
|
1329
|
+
2. Take the machine over: `SEI`, `$7F` to `$DC0D` to mask CIA 1's
|
|
1330
|
+
interrupt sources, and one read of `$DC0D` to clear what was pending.
|
|
1331
|
+
Reading the register clears every bit in it, so any interrupt handler
|
|
1332
|
+
that reads it (the KERNAL's does) would steal the FLAG edges the
|
|
1333
|
+
loader needs. This is also why the KERNAL's tape routines are not
|
|
1334
|
+
called: they own the same register and the same timers and expect
|
|
1335
|
+
their own stream.
|
|
1336
|
+
3. Start CIA 1 Timer B free-running: `$FF` to `$DC06` and `$DC07`, `$11`
|
|
1337
|
+
to `$DC0F` (force load, start, count the system clock, continuous).
|
|
1338
|
+
It is never restarted; each edge's reading is subtracted from the
|
|
1339
|
+
previous one.
|
|
1340
|
+
4. Wait for PLAY (bit 4 of `$01` low), then drive bit 5 of `$01` low for
|
|
1341
|
+
the motor. The KERNAL's interrupt normally does this from its own
|
|
1342
|
+
sense logic; with interrupts off the loader must. Blank the screen
|
|
1343
|
+
(bit 4 of `$D011`) so no badline stretches a poll.
|
|
1344
|
+
5. Per pulse: spin on bit 4 of `$DC0D`; read Timer B high, low, high
|
|
1345
|
+
again, and take both again if the high byte moved (the low byte can
|
|
1346
|
+
wrap in the seven cycles between the reads, and a version that only
|
|
1347
|
+
re-read on a low byte of `$FF` mismeasured one pulse in about sixty
|
|
1348
|
+
by 256 cycles); length is previous less current; the bit is length
|
|
1349
|
+
at or above the threshold, 384 here.
|
|
1350
|
+
6. Sync: count consecutive 1 bits; after sixty-four, the first 0 is bit
|
|
1351
|
+
7 of the sync byte. Rotate seven more bits in and compare with `$5A`;
|
|
1352
|
+
on a mismatch start the count again. Because the tape has run since
|
|
1353
|
+
the KERNAL first saw PLAY, the loader joins the lead-in wherever it
|
|
1354
|
+
happens to be, and this is what makes that harmless.
|
|
1355
|
+
7. Read the length, the block and the checksum, eight bits to a byte,
|
|
1356
|
+
most significant first. Verify, motor off, screen on, `$81` to
|
|
1357
|
+
`$DC0D` to give the KERNAL its Timer A interrupt back, `CLI`.
|
|
1358
|
+
|
|
1359
|
+
### Why it works
|
|
1360
|
+
|
|
1361
|
+
The cassette read line is wired to CIA 1's FLAG input, and the CIA
|
|
1362
|
+
records each falling edge as bit 4 of its interrupt control register
|
|
1363
|
+
whether or not that source is enabled; VICE raises one such edge per
|
|
1364
|
+
TAP entry. The information is entirely in the time between edges, and a
|
|
1365
|
+
free-running 16-bit timer at the system clock measures it to the cycle
|
|
1366
|
+
with no restart cost and no drift, provided the two-byte read is made
|
|
1367
|
+
consistent. The threshold turns a continuous measurement into a bit,
|
|
1368
|
+
and its margin is what absorbs everything that moves the edges: tape
|
|
1369
|
+
speed, the loader's own polling granularity (nine cycles a turn here,
|
|
1370
|
+
and the measured pulses were within 8 cycles of nominal) and the time
|
|
1371
|
+
the loader spends between one poll and the next. The lead-in of one
|
|
1372
|
+
value followed by a sync byte whose first bit is the other value gives
|
|
1373
|
+
byte alignment from a cold start with no marker pulse of a third
|
|
1374
|
+
length, which is the KERNAL's answer to the same problem.
|
|
1375
|
+
|
|
1376
|
+
### Variations
|
|
1377
|
+
|
|
1378
|
+
- **An adaptive threshold.** Measure the lead-in's pulses, which are all
|
|
1379
|
+
the long value, and set the threshold at three quarters of their
|
|
1380
|
+
average (or, with a lead-in that alternates the two values, halfway
|
|
1381
|
+
between the two averages). The loader then follows a tape recorded on
|
|
1382
|
+
a fast or slow deck, or played on one, instead of failing at a fixed
|
|
1383
|
+
figure. Not measured here: the recipe's threshold is a constant. What
|
|
1384
|
+
was measured is the margin it needs: with VICE's default tape speed
|
|
1385
|
+
error and wobble on, the pulse spread grew from 16 cycles to 52 on
|
|
1386
|
+
each value and the block still verified, at least 101 cycles of margin
|
|
1387
|
+
on either side.
|
|
1388
|
+
- **Shorter pulses.** The recipe's per-bit path is about 175 cycles,
|
|
1389
|
+
most of it the minimum and maximum bookkeeping for the report, and a
|
|
1390
|
+
208-cycle 0 pulse failed on it (one bit read long after a 170-cycle
|
|
1391
|
+
setup between two bytes). A loader with nothing in the path but the
|
|
1392
|
+
poll, the timer read, the compare and the rotate can run pulses well
|
|
1393
|
+
under 200 cycles; the floor for a given loop is its longest path
|
|
1394
|
+
between two polls, not its average. Not measured here.
|
|
1395
|
+
- **The FLAG interrupt instead of a poll.** Enable bit 4 in `$DC0D`
|
|
1396
|
+
(`$90`) and take the IRQ or, on CIA 2, the NMI; the handler reads the
|
|
1397
|
+
timer and stores a bit while the main code decrunches or draws. The
|
|
1398
|
+
interrupt latency and its variation then eat into the margin in the
|
|
1399
|
+
poll's place. Not built here.
|
|
1400
|
+
- **A counted loop instead of a timer.** Increment a register while
|
|
1401
|
+
waiting for the edge and compare the count with a constant; that is
|
|
1402
|
+
the classic form and costs no CIA. Its unit is the loop's length, so
|
|
1403
|
+
a badline or an interrupt adds whole units, and the constant is
|
|
1404
|
+
specific to the loop. Not built here.
|
|
1405
|
+
- **The mastering side.** A turbo needs its tape written the same way.
|
|
1406
|
+
No host tool for that ships in this knowledge base; the recipe's
|
|
1407
|
+
Python script is the whole of it, and a real cassette would need the
|
|
1408
|
+
TAP played out through a deck or written by a program on the C64 that
|
|
1409
|
+
drives the write line through bit 3 of `$01` with a timer, as the
|
|
1410
|
+
KERNAL's SAVE does. Not built here.
|
|
1411
|
+
|
|
1412
|
+
### Cycle budget
|
|
1413
|
+
|
|
1414
|
+
Measured, VICE 3.10, `-warp`, screen blanked, tape speed error and
|
|
1415
|
+
wobble off (rung 1): 1,540,498 cycles on PAL and 1,540,502 on NTSC from
|
|
1416
|
+
the end of the sync byte to the end of the checksum, 503 bytes, which is 3,062
|
|
1417
|
+
cycles a byte and 383 cycles a bit for this block's mix of ones and
|
|
1418
|
+
zeros (256 for a 0, 512 for a 1). The same cycles are 321 bytes a
|
|
1419
|
+
second at the PAL clock and 334 at NTSC: the TAP stores cycles, so a
|
|
1420
|
+
tape mastered for one region reads at the other's speed and the same
|
|
1421
|
+
thresholds hold. The KERNAL figure it is set against is arithmetic from
|
|
1422
|
+
the measured pulse modes on the formats page: 9,448 cycles a byte for
|
|
1423
|
+
one copy.
|
|
1424
|
+
|
|
1425
|
+
### Recipes
|
|
1426
|
+
|
|
1427
|
+
- `recipes/kickassembler/tape-turbo-loader.md` (the TAP-writing script, the loader, the checksum verdict, bytes per second and pulse ranges on both models, and the run with VICE's tape wobble left on)
|