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,743 @@
|
|
|
1
|
+
---
|
|
2
|
+
recipe: game-tree-search
|
|
3
|
+
toolchain: oscar64
|
|
4
|
+
output_format: PRG
|
|
5
|
+
region: both
|
|
6
|
+
techniques: [game_tree_search]
|
|
7
|
+
file_formats: [PRG]
|
|
8
|
+
uses_registers: [D011, D012, D019, D01A, D020, D021, DC04, DC05, DC06, DC07, DC0E, DC0F]
|
|
9
|
+
uses_kernal: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!-- doc-type: recipe -->
|
|
13
|
+
|
|
14
|
+
# Oscar64 Game Tree Search: Connect Four by negamax with alpha-beta
|
|
15
|
+
|
|
16
|
+
## Synopsis
|
|
17
|
+
|
|
18
|
+
The machine plays Connect Four (7 columns, 6 rows) against itself: a
|
|
19
|
+
negamax search with alpha-beta pruning, five plies deep, columns tried
|
|
20
|
+
centre first, leaves scored by an incremental cell-weight sum. The search
|
|
21
|
+
is a loop over an explicit per-ply record, not a recursive function, so
|
|
22
|
+
it can stop after any node and go on in a later frame. The game runs to a
|
|
23
|
+
full board (a draw at this depth) three times: blocking, with each search
|
|
24
|
+
in one call; one node a call, to time the worst single node; and sliced,
|
|
25
|
+
four nodes a frame while a per-frame counter keeps running. Every column
|
|
26
|
+
chosen and the node count of every one of the 42 searches in each game is
|
|
27
|
+
compared with a Python model of the same search, compiled in. The program
|
|
28
|
+
prints PASS or FAIL, nodes per second and cycles per node from a CIA1
|
|
29
|
+
timer, frames per move from a raster IRQ counter, the worst single call
|
|
30
|
+
and the worst slice. It implements `game_tree_search`
|
|
31
|
+
(`techniques/logic.md`). Build `-dRECURSIVE=1` for the blocking game with
|
|
32
|
+
the search written as a recursive function.
|
|
33
|
+
|
|
34
|
+
## Source
|
|
35
|
+
|
|
36
|
+
```c
|
|
37
|
+
// game-tree-search.c
|
|
38
|
+
// Connect Four (7 columns, 6 rows) played by the machine against itself:
|
|
39
|
+
// negamax with alpha-beta to a fixed depth, centre-first move order and an
|
|
40
|
+
// incremental cell-weight evaluation. The search is iterative, with one
|
|
41
|
+
// record per ply in static arrays, so it can stop after any node and go on
|
|
42
|
+
// later: search_step(n) runs at most n nodes. The game is played three
|
|
43
|
+
// times: blocking (each search in one call), one node a call (to time the
|
|
44
|
+
// worst single node), and sliced (SLICE nodes a frame while a per-frame
|
|
45
|
+
// counter keeps running). Every chosen column and node count of each game
|
|
46
|
+
// is checked against a Python model of the same search.
|
|
47
|
+
// Build -dRECURSIVE=1 for the blocking game with a recursive search.
|
|
48
|
+
#include <c64/vic.h>
|
|
49
|
+
#include <c64/cia.h>
|
|
50
|
+
#include <c64/rasterirq.h>
|
|
51
|
+
|
|
52
|
+
#ifndef DEPTH
|
|
53
|
+
#define DEPTH 5 // plies searched from each position
|
|
54
|
+
#endif
|
|
55
|
+
#ifndef SLICE
|
|
56
|
+
#define SLICE 4 // nodes a frame in the sliced game
|
|
57
|
+
#endif
|
|
58
|
+
#ifndef RECURSIVE
|
|
59
|
+
#define RECURSIVE 0 // 1: blocking game only, recursive search
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
#define SCREEN ((char *)0x0400)
|
|
63
|
+
#define COLOUR ((char *)0xd800)
|
|
64
|
+
#define EMPTY 0
|
|
65
|
+
#define EDGE 3 // sentinel: never equal to a player
|
|
66
|
+
#define WIN 1000 // a win found at ply p scores WIN - p
|
|
67
|
+
#define INF 30000
|
|
68
|
+
#define MAXMOVES 42
|
|
69
|
+
|
|
70
|
+
// The Python model's game at DEPTH 5, a draw on a full board: the column
|
|
71
|
+
// chosen and the nodes searched for each of the 42 moves.
|
|
72
|
+
#define NEXP 42
|
|
73
|
+
static const char exp_col[NEXP] = {3, 3, 2, 4, 3, 3, 3, 3, 2, 2, 2, 4, 2, 1,
|
|
74
|
+
1, 4, 4, 4, 2, 4, 5, 1, 1, 1, 1, 0, 0, 0, 5, 5, 0, 0, 0, 6, 5, 5, 5, 6, 6,
|
|
75
|
+
6, 6, 6};
|
|
76
|
+
static const unsigned exp_nodes[NEXP] = {755, 563, 1484, 941, 2283, 702, 2360,
|
|
77
|
+
908, 1342, 341, 1177, 746, 1024, 637, 658, 525, 347, 494, 415, 177, 124,
|
|
78
|
+
164, 80, 172, 142, 72, 119, 72, 105, 53, 107, 64, 58, 27, 23, 9, 7, 5, 4,
|
|
79
|
+
3, 2, 1};
|
|
80
|
+
|
|
81
|
+
// Board: 8 rows of 8 bytes. Rows 1..6 are the play rows (row 1 at the
|
|
82
|
+
// bottom), columns 0..6. Row 0, row 7 and column 7 hold EDGE, so a run
|
|
83
|
+
// along any of the four directions stops at the edge without a bounds test.
|
|
84
|
+
static char board[64];
|
|
85
|
+
static char height[7]; // pieces in each column
|
|
86
|
+
static int score; // sum of weights: player 1 plus, player 2 minus
|
|
87
|
+
static char nmoves; // pieces on the board
|
|
88
|
+
static unsigned nodes; // positions made by the current search
|
|
89
|
+
|
|
90
|
+
// Number of four-in-a-row windows through each cell, row 1 first.
|
|
91
|
+
static const char weight[64] = {
|
|
92
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
93
|
+
3, 4, 5, 7, 5, 4, 3, 0,
|
|
94
|
+
4, 6, 8,10, 8, 6, 4, 0,
|
|
95
|
+
5, 8,11,13,11, 8, 5, 0,
|
|
96
|
+
5, 8,11,13,11, 8, 5, 0,
|
|
97
|
+
4, 6, 8,10, 8, 6, 4, 0,
|
|
98
|
+
3, 4, 5, 7, 5, 4, 3, 0,
|
|
99
|
+
0, 0, 0, 0, 0, 0, 0, 0 };
|
|
100
|
+
static const char order[7] = { 3, 2, 4, 1, 5, 0, 6 }; // centre first
|
|
101
|
+
static const char dirs[4] = { 1, 8, 9, 7 };
|
|
102
|
+
|
|
103
|
+
// Does the piece just placed at cell i complete four for player p?
|
|
104
|
+
static char wins(char i, char p)
|
|
105
|
+
{
|
|
106
|
+
for (char k = 0; k < 4; k++)
|
|
107
|
+
{
|
|
108
|
+
char d = dirs[k], n = 1, j = i + d;
|
|
109
|
+
while (board[j] == p) { n++; j += d; }
|
|
110
|
+
j = i - d;
|
|
111
|
+
while (board[j] == p) { n++; j -= d; }
|
|
112
|
+
if (n >= 4) return 1;
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Returns the cell index. The new height goes through a variable: written
|
|
118
|
+
// as i = ((height[c] + 1) << 3) + c; board[i] = side; height[c]++;
|
|
119
|
+
// Oscar64 1.32.271 stores side + 1 into height[c] (see "Why this works").
|
|
120
|
+
static char make(char c, char side)
|
|
121
|
+
{
|
|
122
|
+
char h = height[c] + 1;
|
|
123
|
+
height[c] = h;
|
|
124
|
+
char i = (h << 3) + c;
|
|
125
|
+
board[i] = side; nmoves++;
|
|
126
|
+
if (side == 1) score += weight[i]; else score -= weight[i];
|
|
127
|
+
return i;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static void unmake(char c, char i, char side)
|
|
131
|
+
{
|
|
132
|
+
board[i] = EMPTY; height[c]--; nmoves--;
|
|
133
|
+
if (side == 1) score -= weight[i]; else score += weight[i];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The value of the move just made, for the side that made it, when the
|
|
137
|
+
// search stops here: a win, a full board, or the depth limit. Otherwise
|
|
138
|
+
// returns 1 in *go and the caller searches below.
|
|
139
|
+
static int leaf(char i, char side, char ply, char *go)
|
|
140
|
+
{
|
|
141
|
+
*go = 0;
|
|
142
|
+
if (wins(i, side)) return WIN - ply;
|
|
143
|
+
if (nmoves == MAXMOVES) return 0;
|
|
144
|
+
if (ply + 1 == DEPTH) return side == 1 ? score : -score;
|
|
145
|
+
*go = 1;
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static char bestcol; // the root's choice
|
|
150
|
+
|
|
151
|
+
#if RECURSIVE
|
|
152
|
+
static int negamax(char ply, int alpha, int beta, char side)
|
|
153
|
+
{
|
|
154
|
+
int best = -INF;
|
|
155
|
+
for (char k = 0; k < 7; k++)
|
|
156
|
+
{
|
|
157
|
+
char c = order[k];
|
|
158
|
+
if (height[c] == 6) continue;
|
|
159
|
+
char i = make(c, side), go;
|
|
160
|
+
nodes++;
|
|
161
|
+
int v = leaf(i, side, ply, &go);
|
|
162
|
+
if (go) v = -negamax(ply + 1, -beta, -alpha, 3 - side);
|
|
163
|
+
unmake(c, i, side);
|
|
164
|
+
if (v > best)
|
|
165
|
+
{
|
|
166
|
+
best = v;
|
|
167
|
+
if (ply == 0) bestcol = c;
|
|
168
|
+
if (v > alpha) { alpha = v; if (alpha >= beta) break; }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return best;
|
|
172
|
+
}
|
|
173
|
+
static void search_begin(char side) { nodes = 0; negamax(0, -INF, INF, side); }
|
|
174
|
+
static char search_step(unsigned budget) { return 1; }
|
|
175
|
+
#else
|
|
176
|
+
// One record per ply: the window, the best value so far, the next entry
|
|
177
|
+
// of order[] to try, and the move made from this ply (column and cell).
|
|
178
|
+
// With the ply and side to move, this is the whole state of a search.
|
|
179
|
+
static int p_alpha[DEPTH], p_beta[DEPTH], p_best[DEPTH];
|
|
180
|
+
static char p_next[DEPTH], p_col[DEPTH], p_cell[DEPTH];
|
|
181
|
+
static char s_ply, s_side;
|
|
182
|
+
|
|
183
|
+
static void search_begin(char side)
|
|
184
|
+
{
|
|
185
|
+
nodes = 0; s_ply = 0; s_side = side;
|
|
186
|
+
p_alpha[0] = -INF; p_beta[0] = INF; p_best[0] = -INF; p_next[0] = 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Runs the search for at most budget nodes. Returns 1 when it has
|
|
190
|
+
// finished (bestcol holds the move), 0 when it stopped to go on later.
|
|
191
|
+
static char search_step(unsigned budget)
|
|
192
|
+
{
|
|
193
|
+
char ply = s_ply, side = s_side;
|
|
194
|
+
for (;;)
|
|
195
|
+
{
|
|
196
|
+
int v;
|
|
197
|
+
if (p_next[ply] == 7)
|
|
198
|
+
{
|
|
199
|
+
// Every child of this ply is done: return its value to the parent.
|
|
200
|
+
if (ply == 0) return 1;
|
|
201
|
+
v = -p_best[ply];
|
|
202
|
+
ply--; side = 3 - side;
|
|
203
|
+
unmake(p_col[ply], p_cell[ply], side);
|
|
204
|
+
}
|
|
205
|
+
else
|
|
206
|
+
{
|
|
207
|
+
char c = order[p_next[ply]];
|
|
208
|
+
if (height[c] == 6) { p_next[ply]++; continue; }
|
|
209
|
+
if (budget == 0) { s_ply = ply; s_side = side; return 0; }
|
|
210
|
+
budget--;
|
|
211
|
+
p_next[ply]++;
|
|
212
|
+
char i = make(c, side), go;
|
|
213
|
+
p_col[ply] = c; p_cell[ply] = i;
|
|
214
|
+
nodes++;
|
|
215
|
+
v = leaf(i, side, ply, &go);
|
|
216
|
+
if (go)
|
|
217
|
+
{
|
|
218
|
+
// Descend: the child's window is the parent's, negated and swapped.
|
|
219
|
+
ply++; side = 3 - side;
|
|
220
|
+
p_alpha[ply] = -p_beta[ply - 1]; p_beta[ply] = -p_alpha[ply - 1];
|
|
221
|
+
p_best[ply] = -INF; p_next[ply] = 0;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
unmake(c, i, side);
|
|
225
|
+
}
|
|
226
|
+
// v is the value of the move p_col[ply] for the side to move at ply.
|
|
227
|
+
if (v > p_best[ply])
|
|
228
|
+
{
|
|
229
|
+
p_best[ply] = v;
|
|
230
|
+
if (ply == 0) bestcol = p_col[0];
|
|
231
|
+
if (v > p_alpha[ply])
|
|
232
|
+
{
|
|
233
|
+
p_alpha[ply] = v;
|
|
234
|
+
if (v >= p_beta[ply]) p_next[ply] = 7; // cut: skip the rest
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
#endif
|
|
240
|
+
|
|
241
|
+
// --- the demonstration: play, check, time, draw ---------------------------
|
|
242
|
+
static void put_text(char row, char col, const char *s)
|
|
243
|
+
{
|
|
244
|
+
char *p = SCREEN + 40 * row + col;
|
|
245
|
+
while (*s)
|
|
246
|
+
{
|
|
247
|
+
char c = *s++;
|
|
248
|
+
if (c >= 'A' && c <= 'Z') c -= 64;
|
|
249
|
+
*p++ = c;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// v as n decimal digits (n = 1..8), leading zeros shown.
|
|
253
|
+
static void put_dec(char row, char col, unsigned long v, char n)
|
|
254
|
+
{
|
|
255
|
+
static const unsigned long pw[8] = { 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
|
|
256
|
+
char *p = SCREEN + 40 * row + col;
|
|
257
|
+
for (char i = 8 - n; i < 8; i++)
|
|
258
|
+
{
|
|
259
|
+
char d = 0x30;
|
|
260
|
+
while (v >= pw[i]) { v -= pw[i]; d++; }
|
|
261
|
+
*p++ = d;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// CIA1 timer A counts phi2 from $FFFF; timer B counts A's underflows.
|
|
265
|
+
static void t_start(void)
|
|
266
|
+
{
|
|
267
|
+
cia1.cra = 0; cia1.crb = 0;
|
|
268
|
+
cia1.ta = 0xffff; cia1.tb = 0xffff;
|
|
269
|
+
cia1.crb = 0x51; // force load, start, count A underflows
|
|
270
|
+
cia1.cra = 0x11; // force load, start, count phi2
|
|
271
|
+
}
|
|
272
|
+
static unsigned long t_stop(void)
|
|
273
|
+
{
|
|
274
|
+
cia1.cra = 0; cia1.crb = 0;
|
|
275
|
+
return ((unsigned long)(0xffff - cia1.tb) << 16) + (0xffff - cia1.ta);
|
|
276
|
+
}
|
|
277
|
+
static unsigned long c_null; // t_start plus t_stop with nothing between
|
|
278
|
+
|
|
279
|
+
static void put_cell(char i) // board cell i to the screen
|
|
280
|
+
{
|
|
281
|
+
char r = (i >> 3) - 1, c = i & 7, b = board[i];
|
|
282
|
+
unsigned o = 40 * (6 - r) + 2 * c + 1; // row 1 at screen row 6
|
|
283
|
+
SCREEN[o] = b == EMPTY ? 0x2e : 0x51;
|
|
284
|
+
COLOUR[o] = b == 1 ? VCOL_YELLOW : b == 2 ? VCOL_RED : VCOL_MED_GREY;
|
|
285
|
+
}
|
|
286
|
+
static void reset(void)
|
|
287
|
+
{
|
|
288
|
+
for (char i = 0; i < 64; i++) board[i] = EDGE;
|
|
289
|
+
for (char r = 1; r < 7; r++)
|
|
290
|
+
for (char c = 0; c < 7; c++) board[(r << 3) + c] = EMPTY;
|
|
291
|
+
for (char c = 0; c < 7; c++) height[c] = 0;
|
|
292
|
+
score = 0; nmoves = 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
RIRQCode frame_irq;
|
|
296
|
+
volatile unsigned ticks;
|
|
297
|
+
__interrupt void on_frame(void) { ticks++; }
|
|
298
|
+
|
|
299
|
+
// The result of one game.
|
|
300
|
+
static char played, winner, okcol, oknodes;
|
|
301
|
+
// Called when the search for move 'played' has finished: check it against
|
|
302
|
+
// the model, play it, and say whether the game is over.
|
|
303
|
+
static char finish_move(char side, char draw)
|
|
304
|
+
{
|
|
305
|
+
if (played < NEXP)
|
|
306
|
+
{
|
|
307
|
+
if (bestcol == exp_col[played]) okcol++;
|
|
308
|
+
if (nodes == exp_nodes[played]) oknodes++;
|
|
309
|
+
}
|
|
310
|
+
char i = make(bestcol, side);
|
|
311
|
+
if (draw)
|
|
312
|
+
{
|
|
313
|
+
put_cell(i);
|
|
314
|
+
// the columns played, 21 to a row, on rows 9 and 10
|
|
315
|
+
SCREEN[40 * (9 + played / 21) + 4 + played % 21] = 0x30 + bestcol;
|
|
316
|
+
}
|
|
317
|
+
played++;
|
|
318
|
+
if (wins(i, side)) { winner = side; return 1; }
|
|
319
|
+
return played == MAXMOVES;
|
|
320
|
+
}
|
|
321
|
+
static void new_game(void) { reset(); played = 0; winner = 0; okcol = 0; oknodes = 0; }
|
|
322
|
+
static void put_check(char row)
|
|
323
|
+
{
|
|
324
|
+
put_text(row, 0, " COLS / NODES /");
|
|
325
|
+
put_dec(row, 8, okcol, 2); put_dec(row, 11, NEXP, 2);
|
|
326
|
+
put_dec(row, 20, oknodes, 2); put_dec(row, 23, NEXP, 2);
|
|
327
|
+
}
|
|
328
|
+
static char game_ok(void)
|
|
329
|
+
{
|
|
330
|
+
return played == NEXP && okcol == NEXP && oknodes == NEXP && winner == 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
int main(void)
|
|
334
|
+
{
|
|
335
|
+
__asm { sei }
|
|
336
|
+
vic.color_border = VCOL_BLACK;
|
|
337
|
+
vic.color_back = VCOL_BLACK;
|
|
338
|
+
for (unsigned i = 0; i < 1000; i++) { SCREEN[i] = 0x20; COLOUR[i] = VCOL_WHITE; }
|
|
339
|
+
|
|
340
|
+
// PAL or NTSC: the highest raster line reached past 255.
|
|
341
|
+
while (!(vic.ctrl1 & 0x80)) ;
|
|
342
|
+
char top = 0;
|
|
343
|
+
while (vic.ctrl1 & 0x80) { char r = vic.raster; if (r > top) top = r; }
|
|
344
|
+
char pal = top > 0x20; // PAL reaches $137, NTSC $106
|
|
345
|
+
|
|
346
|
+
t_start(); c_null = t_stop();
|
|
347
|
+
|
|
348
|
+
rirq_init(true);
|
|
349
|
+
rirq_build(&frame_irq, 1);
|
|
350
|
+
rirq_call(&frame_irq, 0, on_frame);
|
|
351
|
+
rirq_set(0, 250, &frame_irq);
|
|
352
|
+
rirq_sort();
|
|
353
|
+
rirq_start();
|
|
354
|
+
|
|
355
|
+
// Game 1, blocking: each search runs to the end in one call.
|
|
356
|
+
unsigned long allnodes = 0, allcyc = 0, cmax = 0;
|
|
357
|
+
unsigned fmin = 0xffff, fmax = 0, total = 0;
|
|
358
|
+
char side = 1, pass;
|
|
359
|
+
new_game();
|
|
360
|
+
for (;;)
|
|
361
|
+
{
|
|
362
|
+
unsigned f0 = ticks;
|
|
363
|
+
t_start();
|
|
364
|
+
search_begin(side);
|
|
365
|
+
search_step(0xffff);
|
|
366
|
+
unsigned long cyc = t_stop() - c_null;
|
|
367
|
+
unsigned fr = ticks - f0;
|
|
368
|
+
allnodes += nodes; allcyc += cyc; total += fr;
|
|
369
|
+
if (cyc > cmax) cmax = cyc;
|
|
370
|
+
if (fr < fmin) fmin = fr;
|
|
371
|
+
if (fr > fmax) fmax = fr;
|
|
372
|
+
if (finish_move(side, RECURSIVE)) break;
|
|
373
|
+
side = 3 - side;
|
|
374
|
+
}
|
|
375
|
+
pass = game_ok();
|
|
376
|
+
put_text(11, 0, "BLOCKING"); put_check(12);
|
|
377
|
+
put_text(13, 0, " NODES CYCLES");
|
|
378
|
+
put_dec(13, 9, allnodes, 6); put_dec(13, 24, allcyc, 8);
|
|
379
|
+
// nodes per second = nodes * clock / cycles, scaled to stay in 32 bits
|
|
380
|
+
unsigned long clk100 = pal ? 9852 : 10227; // phi2 / 100: PAL 985,248 Hz, NTSC 1,022,727 Hz
|
|
381
|
+
unsigned long nps = allnodes * clk100 / (allcyc / 100);
|
|
382
|
+
put_text(14, 0, " NODES/S CYC/NODE");
|
|
383
|
+
put_dec(14, 11, nps, 5); put_dec(14, 26, allcyc / allnodes, 5);
|
|
384
|
+
put_text(15, 0, " FRAMES/MOVE MIN MAX ALL");
|
|
385
|
+
put_dec(15, 19, fmin, 3); put_dec(15, 27, fmax, 3); put_dec(15, 35, total, 4);
|
|
386
|
+
put_text(16, 0, " SLOWEST MOVE CYCLES");
|
|
387
|
+
put_dec(16, 16, cmax, 7);
|
|
388
|
+
|
|
389
|
+
#if !RECURSIVE
|
|
390
|
+
// Game 2: one node a call, each call timed: the worst single step is
|
|
391
|
+
// one node plus any returns up the plies before it.
|
|
392
|
+
unsigned long worst1 = 0;
|
|
393
|
+
side = 1;
|
|
394
|
+
new_game();
|
|
395
|
+
for (;;)
|
|
396
|
+
{
|
|
397
|
+
search_begin(side);
|
|
398
|
+
char done;
|
|
399
|
+
do
|
|
400
|
+
{
|
|
401
|
+
t_start();
|
|
402
|
+
done = search_step(1);
|
|
403
|
+
unsigned long c = t_stop() - c_null;
|
|
404
|
+
if (c > worst1) worst1 = c;
|
|
405
|
+
} while (!done);
|
|
406
|
+
if (finish_move(side, 0)) break;
|
|
407
|
+
side = 3 - side;
|
|
408
|
+
}
|
|
409
|
+
pass &= game_ok();
|
|
410
|
+
put_text(17, 0, "ONE NODE A CALL"); put_check(18);
|
|
411
|
+
put_text(19, 0, " WORST CALL CYCLES");
|
|
412
|
+
put_dec(19, 14, worst1, 5);
|
|
413
|
+
|
|
414
|
+
// Game 3, sliced: SLICE nodes a frame. Each frame the loop waits for
|
|
415
|
+
// the raster IRQ, advances a spinner and frame count, and runs one
|
|
416
|
+
// slice; the frame that follows a finished search plays the move.
|
|
417
|
+
static const char spin[4] = { 0x2d, 0x4e, 0x5d, 0x4d }; // - / | \ in screen codes
|
|
418
|
+
unsigned long worstn = 0;
|
|
419
|
+
unsigned frames = 0, missed = 0, mf = 0, mfmax = 0;
|
|
420
|
+
for (char i = 0; i < 64; i++) if (board[i] != EDGE) { board[i] = EMPTY; put_cell(i); }
|
|
421
|
+
side = 1;
|
|
422
|
+
new_game();
|
|
423
|
+
search_begin(side);
|
|
424
|
+
char searching = 1;
|
|
425
|
+
put_text(22, 0, "FRAME");
|
|
426
|
+
unsigned last = ticks;
|
|
427
|
+
for (;;)
|
|
428
|
+
{
|
|
429
|
+
while (ticks == last) ;
|
|
430
|
+
unsigned now = ticks;
|
|
431
|
+
missed += now - last - 1;
|
|
432
|
+
last = now;
|
|
433
|
+
frames++; mf++;
|
|
434
|
+
SCREEN[40 * 22 + 12] = spin[frames & 3];
|
|
435
|
+
put_dec(22, 6, frames, 5);
|
|
436
|
+
if (searching)
|
|
437
|
+
{
|
|
438
|
+
t_start();
|
|
439
|
+
char done = search_step(SLICE);
|
|
440
|
+
unsigned long c = t_stop() - c_null;
|
|
441
|
+
if (c > worstn) worstn = c;
|
|
442
|
+
if (done) searching = 0;
|
|
443
|
+
}
|
|
444
|
+
else
|
|
445
|
+
{
|
|
446
|
+
if (mf > mfmax) mfmax = mf;
|
|
447
|
+
mf = 0;
|
|
448
|
+
if (finish_move(side, 1)) break;
|
|
449
|
+
side = 3 - side;
|
|
450
|
+
search_begin(side);
|
|
451
|
+
searching = 1;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
pass &= game_ok();
|
|
455
|
+
put_text(20, 0, "SLICED NODES A FRAME"); put_dec(20, 7, SLICE, 1);
|
|
456
|
+
put_check(21);
|
|
457
|
+
put_text(22, 14, "MISSED MOST/MOVE");
|
|
458
|
+
put_dec(22, 21, missed, 5); put_dec(22, 37, mfmax, 3);
|
|
459
|
+
put_text(23, 0, " WORST SLICE CYCLES");
|
|
460
|
+
put_dec(23, 15, worstn, 5);
|
|
461
|
+
#endif
|
|
462
|
+
|
|
463
|
+
put_text(8, 0, "DEPTH MOVES WINNER");
|
|
464
|
+
put_dec(8, 6, DEPTH, 1); put_dec(8, 14, played, 2);
|
|
465
|
+
put_text(8, 24, winner == 1 ? "YELLOW" : winner == 2 ? "RED" : "NONE");
|
|
466
|
+
put_text(9, 0, "SEQ");
|
|
467
|
+
put_text(24, 0, pal ? "PAL" : "NTSC");
|
|
468
|
+
put_text(24, 5, RECURSIVE ? "RECURSIVE" : "ITERATIVE");
|
|
469
|
+
put_text(24, 16, pass ? "PASS" : "FAIL");
|
|
470
|
+
vic.color_border = pass ? VCOL_GREEN : VCOL_RED;
|
|
471
|
+
for (;;) ;
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Build
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
oscar64 -tm=c64 -O2 -o=game-tree-search.prg game-tree-search.c
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Run headless (PAL; add `-model ntsc` for NTSC):
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
timeout 180 x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 -limitcycles 160000000 -exitscreenshot game-tree-search.png -autostart game-tree-search.prg
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The report is on screen between 135,000,000 and 140,000,000 cycles on
|
|
489
|
+
PAL and between 125,000,000 and 130,000,000 on NTSC (measured by
|
|
490
|
+
bisecting `-limitcycles`); 160,000,000 leaves a margin. The sliced game
|
|
491
|
+
takes most of that: 4,880 frames.
|
|
492
|
+
|
|
493
|
+
## Expected output
|
|
494
|
+
|
|
495
|
+
`screenshots/game-tree-search.png` (PAL) and
|
|
496
|
+
`screenshots/game-tree-search-ntsc.png` (NTSC). Black screen, green
|
|
497
|
+
border. Screen rows 1 to 6 hold the full board, top row first, one
|
|
498
|
+
filled circle (screen code $51) every second column: yellow for the side
|
|
499
|
+
that moved first, red for the other. Top row first, the board reads:
|
|
500
|
+
|
|
501
|
+
```text
|
|
502
|
+
YYYRRYR
|
|
503
|
+
RRYYRRY
|
|
504
|
+
YYYRYYR
|
|
505
|
+
RRRYRRY
|
|
506
|
+
YYYRRYR
|
|
507
|
+
RRYYRYR
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Rows 8 to 24, PAL:
|
|
511
|
+
|
|
512
|
+
```text
|
|
513
|
+
DEPTH 5 MOVES 42 WINNER NONE
|
|
514
|
+
SEQ 332433332224211444245
|
|
515
|
+
111100055000655566666
|
|
516
|
+
BLOCKING
|
|
517
|
+
COLS 42/42 NODES 42/42
|
|
518
|
+
NODES 019292 CYCLES 17397672
|
|
519
|
+
NODES/S 01092 CYC/NODE 00901
|
|
520
|
+
FRAMES/MOVE MIN 000 MAX 103 ALL 0885
|
|
521
|
+
SLOWEST MOVE 2019814 CYCLES
|
|
522
|
+
ONE NODE A CALL
|
|
523
|
+
COLS 42/42 NODES 42/42
|
|
524
|
+
WORST CALL 02301 CYCLES
|
|
525
|
+
SLICED 4 NODES A FRAME
|
|
526
|
+
COLS 42/42 NODES 42/42
|
|
527
|
+
FRAME 04880 - MISSED 00000 MOST/MOVE 591
|
|
528
|
+
WORST SLICE 05024 CYCLES
|
|
529
|
+
PAL ITERATIVE PASS
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
NTSC reads `CYCLES 17601946`, `NODES/S 01120 CYC/NODE 00912`,
|
|
533
|
+
`FRAMES/MOVE MIN 000 MAX 120 ALL 1028`, `SLOWEST MOVE 2043608`,
|
|
534
|
+
`WORST CALL 02329`, `WORST SLICE 05325` and `NTSC`; the rest is the same.
|
|
535
|
+
|
|
536
|
+
- `SEQ` is the column (0 to 6, left to right) of each of the 42 moves of
|
|
537
|
+
the sliced game.
|
|
538
|
+
- `COLS` and `NODES` count the moves whose column, and whose node count,
|
|
539
|
+
equal the model's, once per game. A node is one position made by the
|
|
540
|
+
search, the root's children included.
|
|
541
|
+
- `CYCLES` is the sum over the 42 blocking searches, CIA1 timer A
|
|
542
|
+
cascaded into timer B, display on, with the frame counter's raster IRQ
|
|
543
|
+
running (one interrupt a frame, inside the figure). `NODES/S` is nodes
|
|
544
|
+
times the phi2 clock (985,248 Hz PAL, 1,022,727 Hz NTSC) over cycles;
|
|
545
|
+
NTSC has the faster clock, so it searches more nodes a second while
|
|
546
|
+
spending more cycles on each (a shorter frame and so a larger share of
|
|
547
|
+
badlines and interrupts, not separated here).
|
|
548
|
+
- `FRAMES/MOVE` is the fewest and most display frames one blocking
|
|
549
|
+
search took and the total, from the raster IRQ counter on line 250.
|
|
550
|
+
- `SLOWEST MOVE` is the most cycles one blocking search took: move 7,
|
|
551
|
+
2,360 nodes, about two seconds on either model.
|
|
552
|
+
- `WORST CALL` is the most cycles one `search_step(1)` call took over the
|
|
553
|
+
whole game: one node, any returns up the plies before it, and the
|
|
554
|
+
call itself, timed like `CYCLES`, so it can include badlines and the
|
|
555
|
+
frame interrupt.
|
|
556
|
+
- `FRAME` is the frame count of the sliced game and the character after
|
|
557
|
+
it a spinner, both advanced by the main loop once a frame. `MISSED`
|
|
558
|
+
counts frames in which the loop did not run: the IRQ counter moved on by
|
|
559
|
+
more than one while the loop was busy. `MOST/MOVE` is the most frames
|
|
560
|
+
one sliced move took, the frame that plays it included. `WORST SLICE`
|
|
561
|
+
is the most cycles one `search_step(4)` call took.
|
|
562
|
+
|
|
563
|
+
The model: the same search in Python. Its column list, node list and
|
|
564
|
+
final board are the values compiled into the listing and quoted above.
|
|
565
|
+
|
|
566
|
+
```python
|
|
567
|
+
EMPTY, EDGE, WIN, INF, DEPTH = 0, 3, 1000, 30000, 5
|
|
568
|
+
ROW = [[3, 4, 5, 7, 5, 4, 3], [4, 6, 8, 10, 8, 6, 4], [5, 8, 11, 13, 11, 8, 5]]
|
|
569
|
+
WEIGHT = [0] * 64 # windows of four through each cell
|
|
570
|
+
for r, w in enumerate(ROW + ROW[::-1]):
|
|
571
|
+
WEIGHT[(r + 1) * 8:(r + 1) * 8 + 7] = w
|
|
572
|
+
ORDER, DIRS = [3, 2, 4, 1, 5, 0, 6], [1, 8, 9, 7]
|
|
573
|
+
board = [EDGE] * 64
|
|
574
|
+
for r in range(1, 7):
|
|
575
|
+
board[r * 8:r * 8 + 7] = [EMPTY] * 7
|
|
576
|
+
height, st = [0] * 7, {'score': 0, 'moves': 0, 'nodes': 0}
|
|
577
|
+
|
|
578
|
+
def wins(i, p):
|
|
579
|
+
for d in DIRS:
|
|
580
|
+
n, j = 1, i + d
|
|
581
|
+
while board[j] == p: n, j = n + 1, j + d
|
|
582
|
+
j = i - d
|
|
583
|
+
while board[j] == p: n, j = n + 1, j - d
|
|
584
|
+
if n >= 4: return True
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
def make(c, side):
|
|
588
|
+
i = (height[c] + 1) * 8 + c
|
|
589
|
+
board[i] = side; height[c] += 1; st['moves'] += 1
|
|
590
|
+
st['score'] += WEIGHT[i] if side == 1 else -WEIGHT[i]
|
|
591
|
+
return i
|
|
592
|
+
|
|
593
|
+
def unmake(c, i, side):
|
|
594
|
+
board[i] = EMPTY; height[c] -= 1; st['moves'] -= 1
|
|
595
|
+
st['score'] -= WEIGHT[i] if side == 1 else -WEIGHT[i]
|
|
596
|
+
|
|
597
|
+
def negamax(ply, alpha, beta, side):
|
|
598
|
+
best, bestcol = -INF, None
|
|
599
|
+
for c in ORDER:
|
|
600
|
+
if height[c] == 6: continue
|
|
601
|
+
i = make(c, side); st['nodes'] += 1
|
|
602
|
+
if wins(i, side): v = WIN - ply
|
|
603
|
+
elif st['moves'] == 42: v = 0
|
|
604
|
+
elif ply + 1 == DEPTH: v = st['score'] if side == 1 else -st['score']
|
|
605
|
+
else: v = -negamax(ply + 1, -beta, -alpha, 3 - side)[0]
|
|
606
|
+
unmake(c, i, side)
|
|
607
|
+
if v > best: # strictly better: the first best column stays
|
|
608
|
+
best, bestcol = v, c
|
|
609
|
+
if v > alpha:
|
|
610
|
+
alpha = v
|
|
611
|
+
if alpha >= beta: break
|
|
612
|
+
return best, bestcol
|
|
613
|
+
|
|
614
|
+
side, cols, nodes = 1, [], []
|
|
615
|
+
while st['moves'] < 42:
|
|
616
|
+
st['nodes'] = 0
|
|
617
|
+
col = negamax(0, -INF, INF, side)[1]
|
|
618
|
+
cols.append(col); nodes.append(st['nodes'])
|
|
619
|
+
if wins(make(col, side), side): break
|
|
620
|
+
side = 3 - side
|
|
621
|
+
print(''.join(map(str, cols)), sum(nodes))
|
|
622
|
+
print(nodes)
|
|
623
|
+
for r in range(6, 0, -1): # top row first, as on screen
|
|
624
|
+
print(''.join('.YR'[board[r * 8 + c]] for c in range(7)))
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
## Why this works
|
|
628
|
+
|
|
629
|
+
The board is 64 bytes: eight rows of eight, the six play rows in the
|
|
630
|
+
middle and a sentinel byte (`EDGE`) in row 0, row 7 and column 7. A cell
|
|
631
|
+
is `(row << 3) + column`, and the four line directions are +1, +8, +9
|
|
632
|
+
and +7. `wins()` counts equal pieces outwards from the new piece both
|
|
633
|
+
ways along each direction; a run stops at the first byte that is not the
|
|
634
|
+
player's, and a sentinel never is, so no bounds test is needed. Column 7
|
|
635
|
+
serves both edges of a row: stepping left from column 0 lands on
|
|
636
|
+
column 7 of the row below. A move is a column: `height[c]` gives the
|
|
637
|
+
cell, so move generation is seven tests of `height[c] == 6`.
|
|
638
|
+
|
|
639
|
+
The evaluation is a sum kept up to date by `make()` and `unmake()`: each
|
|
640
|
+
cell's weight is the number of four-in-a-row windows through it (3 at a
|
|
641
|
+
corner, 13 at the centre of the middle rows), added for the first player
|
|
642
|
+
and subtracted for the second. A leaf then costs one load and a negate,
|
|
643
|
+
not a scan of the board. A win scores `WIN - ply`, so a quicker win
|
|
644
|
+
scores higher; a full board scores 0.
|
|
645
|
+
|
|
646
|
+
`search_step()` is negamax: every value is from the view of the side to
|
|
647
|
+
move, and a child's value is negated on the way up. Each ply keeps its
|
|
648
|
+
window (`p_alpha`, `p_beta`), its best value, the index of the next
|
|
649
|
+
column to try and the move it made. Descending sets the child's window to
|
|
650
|
+
the parent's negated and swapped; returning unmakes the parent's move
|
|
651
|
+
and hands up the child's best, negated. A value at least `p_beta` ends
|
|
652
|
+
the ply at once (`p_next = 7`): the opponent above already has a better
|
|
653
|
+
choice and will not allow this line. Ties keep the first column
|
|
654
|
+
(strictly greater replaces), which is why the model must try columns in
|
|
655
|
+
the same order to match node for node.
|
|
656
|
+
|
|
657
|
+
**Time slicing.** `search_step(n)` makes at most `n` nodes. When the
|
|
658
|
+
budget runs out it stores the ply and the side to move in `s_ply` and
|
|
659
|
+
`s_side` and returns 0; the per-ply records already hold the rest of the
|
|
660
|
+
search, so the next call goes on from the same place. The budget is
|
|
661
|
+
tested only before a node is made, so the returns up the plies after a
|
|
662
|
+
slice's last node run at the start of the next slice. The sliced game
|
|
663
|
+
made the same 42 moves with the same node counts as the blocking game and
|
|
664
|
+
the model. Its 4,880 frames match the model exactly: a search of `n`
|
|
665
|
+
nodes takes ceil(n / 4) slices, 4,838 over the game, and each move one
|
|
666
|
+
more frame to play; the slowest move is ceil(2,360 / 4) + 1 = 591.
|
|
667
|
+
`MISSED 00000` on both models shows the main loop ran in every frame, so
|
|
668
|
+
the spinner and the counter never stopped. The cost is time: the slowest
|
|
669
|
+
move takes 591 frames, about 11.8 s on PAL and 9.9 s on NTSC
|
|
670
|
+
(arithmetic), against 103 and 120 frames blocking.
|
|
671
|
+
|
|
672
|
+
**Why four nodes a frame.** The worst single call was 2,301 cycles on
|
|
673
|
+
PAL and 2,329 on NTSC. Four of the NTSC figure, 9,316 cycles, fit in
|
|
674
|
+
half a PAL frame of 9,828 (arithmetic), leaving the other half for the
|
|
675
|
+
game. The bound is loose: the worst slice measured was 5,024 cycles on
|
|
676
|
+
PAL and 5,325 on NTSC, because a slice of four rarely meets four worst
|
|
677
|
+
nodes, and it counts the call overhead four times. A game that needs a
|
|
678
|
+
guarantee plans on the bound.
|
|
679
|
+
|
|
680
|
+
**Iterative or recursive.** The `-dRECURSIVE=1` build plays the same 42
|
|
681
|
+
blocking moves with the same node counts. Oscar64 gives `negamax()` an
|
|
682
|
+
18-byte frame in its software stack region at $9000-$9FFF (the `.asm`
|
|
683
|
+
listing's `SBC #$12` on `SP`), because its static frame allocation cannot
|
|
684
|
+
cover a function that calls itself. Measured with the pinned command
|
|
685
|
+
(not pinned itself), it took 18,941,070 cycles on PAL (981 a node, 109
|
|
686
|
+
frames for the slowest move) against 17,397,672 (901) for the loop, and
|
|
687
|
+
19,164,989 (993) against 17,601,946 (912) on NTSC: about 9 % slower. A
|
|
688
|
+
recursive search also cannot be sliced without keeping its whole call
|
|
689
|
+
chain alive between frames.
|
|
690
|
+
|
|
691
|
+
**Optimisation level.** The whole program, all three games, passes at
|
|
692
|
+
`-O0`, `-O1`, `-O2`, `-O3` and `-Os`. The blocking game's cycles a node
|
|
693
|
+
on PAL were 1,125 at `-O0`, 918 at `-O1`, 901 at `-O2`, 836 at `-Os` and
|
|
694
|
+
780 at `-O3`, the last with a PRG of 10,073 bytes against 4,985 at `-O2`.
|
|
695
|
+
|
|
696
|
+
**An Oscar64 fault met on the way.** A first `make()` wrote
|
|
697
|
+
`char i = ((height[c] + 1) << 3) + c; board[i] = side; height[c]++;` and
|
|
698
|
+
returned `i`. Oscar64 1.32.271 (local build c1270bc, see #25) compiled
|
|
699
|
+
the increment as `LDA side; STA board,y; ADC #$01; STA height,x`: the
|
|
700
|
+
6502 back-end drops the reload of `height[c]` and the `CLC`, so
|
|
701
|
+
`height[c]` gets `side + 1` plus the carry. The intermediate code is
|
|
702
|
+
correct. The full listing with that `make()` played `242424...` and
|
|
703
|
+
matched 10 of 42 columns and no node counts at `-O0`, `-O1`, `-O2` and
|
|
704
|
+
`-Os`; at `-O3` it passed, only because inlining changed the code. A
|
|
705
|
+
short test, `make(3, 2)` through volatile arguments with the returned
|
|
706
|
+
cell stored and then a check that `height[3]` is 1, turns the border red
|
|
707
|
+
at every level from `-O0` to `-Os`. Reading the height into a variable
|
|
708
|
+
first, as the listing does, gives the right result at every level. There
|
|
709
|
+
is no diagnostic. Upstream Oscar64 has later optimiser fixes; this recipe
|
|
710
|
+
was not built with upstream HEAD, and `toolchains/oscar64-reference.md`
|
|
711
|
+
records how HEAD handles the pattern (#30).
|
|
712
|
+
|
|
713
|
+
**Why five plies.** The model's node totals for the whole self-play game
|
|
714
|
+
are 4,670 at depth 4, 19,292 at 5, 59,145 at 6 and 132,100 at 7. At the
|
|
715
|
+
measured 901 cycles a node, depth 5 costs about 17.6 PAL seconds for 42
|
|
716
|
+
blocking moves (arithmetic), and the slowest move two seconds. Depth 7's
|
|
717
|
+
first move alone is 6,062 nodes, about 5.5 s blocking (arithmetic).
|
|
718
|
+
|
|
719
|
+
## Verification
|
|
720
|
+
|
|
721
|
+
VICE x64sc 3.10, windowless build, Oscar64 1.32.271 (local build
|
|
722
|
+
c1270bc), 2026-09-23.
|
|
723
|
+
|
|
724
|
+
**Board and text against the model.** A script read the 42 board cells
|
|
725
|
+
of both pictures, matched each 8x8 cell against `chargen-901225-01.bin`
|
|
726
|
+
and its colour against VICE's palette triples for the model
|
|
727
|
+
(`runtime/vice-reference.md`, "The default palette"): yellow (255, 255,
|
|
728
|
+
70) PAL and (255, 248, 141) NTSC, red (175, 60, 88) and (169, 71, 100).
|
|
729
|
+
0 mismatches with the model's final board on PAL and on NTSC. The `SEQ`
|
|
730
|
+
rows decoded to the model's 42 columns. The text rows were decoded the
|
|
731
|
+
same way and read as quoted above; the border is (98, 213, 50) on PAL
|
|
732
|
+
and (114, 189, 103) on NTSC, colour 5 on each.
|
|
733
|
+
|
|
734
|
+
**Reproducibility.** The pinned command was run twice per model; the PNG
|
|
735
|
+
bytes were identical each time.
|
|
736
|
+
|
|
737
|
+
## Sources
|
|
738
|
+
|
|
739
|
+
- `cave-scan.md` and `print-number.md`: the CIA1 cascaded timer and the
|
|
740
|
+
raster IRQ frame counter, reused in shape.
|
|
741
|
+
- `toolchains/oscar64-reference.md`, "Avoid recursion and function
|
|
742
|
+
pointers": the static call graph and recursion; the cost of the
|
|
743
|
+
recursive form was measured here.
|