diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 0ccfea3a6..6b442affd 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -11,6 +11,7 @@ on: - "exports/**" # Every script the build runs or imports, and no other # (tests/test_workflow_paths.py). + - "scripts/alternatives.py" - "scripts/archive_build.py" - "scripts/artifacts.py" - "scripts/auto_fetch.py" diff --git a/emulators/linuxloader-libretro.yml b/emulators/linuxloader-libretro.yml index d2876faeb..376d12083 100644 --- a/emulators/linuxloader-libretro.yml +++ b/emulators/linuxloader-libretro.yml @@ -108,7 +108,7 @@ files: sha1: "c3480f87336de0407490f4776d78c2baf9d3d965" description: "GNU C++ runtime 5 linked by the older game binaries" source_ref: "src/loader/elfLoader/symbolResolver.cpp:308-377, scripts/appimage/make-appimage.sh:30" - note: "The Linux AppDir takes it from the build image's i386 packages, unpinned; the sha1 is that of the copy in ll-deps/, which the Windows loader reads." + note: "The Linux AppDir takes it from the build image's i386 packages, unpinned. The sha1 is that of the copy in ll-deps/, which the Windows loader reads." - name: "libstdc++.so.6" system: sega-lindbergh @@ -118,7 +118,7 @@ files: sha1: "5ccc8d96d9de310a597a98cc57947b0bcdcbe238" description: "GNU C++ runtime 6 linked by the newer game binaries" source_ref: "src/loader/elfLoader/symbolResolver.cpp:308-377, scripts/appimage/make-appimage.sh:37" - note: "The Linux AppDir takes it from the build image's i386 packages, unpinned; the sha1 is that of the copy in ll-deps/, which the Windows loader reads." + note: "The Linux AppDir takes it from the build image's i386 packages, unpinned. The sha1 is that of the copy in ll-deps/, which the Windows loader reads." - name: "libncurses.so.5" system: sega-lindbergh @@ -128,7 +128,7 @@ files: sha1: "1dd354eb156031d848e947df070d0b436647bfef" description: "ncurses 5 linked by some game binaries" source_ref: "src/loader/elfLoader/symbolResolver.cpp:308-377, scripts/appimage/make-appimage.sh:28" - note: "The Linux AppDir takes it from the build image's i386 packages, unpinned; the sha1 is that of the copy in ll-deps/, which the Windows loader reads." + note: "The Linux AppDir takes it from the build image's i386 packages, unpinned. The sha1 is that of the copy in ll-deps/, which the Windows loader reads." - name: "libpcsclite.so.1" system: sega-lindbergh @@ -138,7 +138,7 @@ files: sha1: "152c306568e1761981ffaa31f030859fe6eb3ec5" description: "PC/SC smart card library linked by the card reader titles" source_ref: "src/loader/elfLoader/symbolResolver.cpp:308-377, scripts/appimage/make-appimage.sh:29" - note: "The Linux AppDir takes it from the build image's i386 packages, unpinned; the sha1 is that of the copy in ll-deps/, which the Windows loader reads." + note: "The Linux AppDir takes it from the build image's i386 packages, unpinned. The sha1 is that of the copy in ll-deps/, which the Windows loader reads." - name: "libcrypto.so.0.9.7" system: sega-lindbergh diff --git a/emulators/openmsx-libretro.yml b/emulators/openmsx-libretro.yml index 6db0dcd39..98df73e1b 100644 --- a/emulators/openmsx-libretro.yml +++ b/emulators/openmsx-libretro.yml @@ -86,7 +86,7 @@ files: sha1: "49a1750c10e407293af6bce27a02e99307ceba12" required: false validation: [sha1] - description: "Read by machine Yamaha_CX5M; extension Yamaha_SFG-01" + description: "Read by machine Yamaha_CX5M and extension Yamaha_SFG-01" source_ref: "share/machines/Yamaha_CX5M.xml:80-83, share/extensions/Yamaha_SFG-01.xml:31-34" - name: "NMS8245SystemROM1.08.bin" @@ -209,9 +209,9 @@ files: sha1: "12f2cc79b3d09723840bae774be48c0d721ec1c6" required: false validation: [sha1] - description: "Read by machine Sony_HB-F1XD; extensions Sony_HBD-20W, Sony_HBD-F1" + description: "Read by machine Sony_HB-F1XD and extensions Sony_HBD-20W, Sony_HBD-F1" source_ref: "share/machines/Sony_HB-F1XD.xml:95-98, share/extensions/Sony_HBD-20W.xml:21-24, share/extensions/Sony_HBD-F1.xml:21-24" - note: "share/extensions/Sony_HBD-20W.xml names it hbd-20w.rom; share/extensions/Sony_HBD-F1.xml names it hbd-f1.rom." + note: "share/extensions/Sony_HBD-20W.xml names it hbd-20w.rom, and share/extensions/Sony_HBD-F1.xml names it hbd-f1.rom." - name: "hb-f1xv_kanjifont.rom" aliases: ["hbi-j1_kanjifont.rom"] @@ -219,7 +219,7 @@ files: sha1: "218d91eb6df2823c924d3774a9f455492a10aecb" required: false validation: [sha1] - description: "Read by machine Sony_HB-F1XV; extension Sony_HBI-J1" + description: "Read by machine Sony_HB-F1XV and extension Sony_HBI-J1" source_ref: "share/machines/Sony_HB-F1XV.xml:30-33, share/extensions/Sony_HBI-J1.xml:15-18" note: "share/extensions/Sony_HBI-J1.xml names it hbi-j1_kanjifont.rom." @@ -245,7 +245,7 @@ files: sha1: "ade0c5ba5574f8114d7079050317099b4519e88f" required: false validation: [sha1] - description: "Read by machine Sony_HB-F1XV; extension Sony_HBI-J1" + description: "Read by machine Sony_HB-F1XV and extension Sony_HBI-J1" source_ref: "share/machines/Sony_HB-F1XV.xml:108-111, share/extensions/Sony_HBI-J1.xml:27-30" note: "share/extensions/Sony_HBI-J1.xml names it hbi-j1_msx-je.rom." @@ -293,7 +293,7 @@ files: validation: [sha1] description: "Read by machines Panasonic_FS-A1WSX, Panasonic_FS-A1ST, Panasonic_FS-A1GT" source_ref: "share/machines/Panasonic_FS-A1WSX.xml:28-31, share/machines/Panasonic_FS-A1ST.xml:32-35, share/machines/Panasonic_FS-A1GT.xml:140-143" - note: "share/machines/Panasonic_FS-A1ST.xml names it fs-a1st_kanjifont.rom; share/machines/Panasonic_FS-A1GT.xml names it fs-a1gt_kanjifont.rom." + note: "share/machines/Panasonic_FS-A1ST.xml names it fs-a1st_kanjifont.rom, and share/machines/Panasonic_FS-A1GT.xml names it fs-a1gt_kanjifont.rom." - name: "fs-a1wsx_IC18.rom" path: "openmsx/share/systemroms/fs-a1wsx_IC18.rom" diff --git a/schemas/install-manifest.schema.json b/schemas/install-manifest.schema.json index 51eaf7f50..350b9b49f 100644 --- a/schemas/install-manifest.schema.json +++ b/schemas/install-manifest.schema.json @@ -543,7 +543,7 @@ } }, "roms_directory": { - "description": "Where the platform keeps its roms root, present when an entry carries root: roms. from_bios is that root relative to the BIOS folder (\".\" for the folder itself, else one or two \"..\" then plain names), as platforms/romfolders/.yml records it from the OS's own source (roms_root.from_bios). It is followed only from a BIOS folder named bios_folder (the platform's base_destination); any other leaves root: roms entries out (no_roms_directory).", + "description": "Where the platform keeps its roms root, present when an entry carries root: roms. from_bios is that root relative to the BIOS folder (\".\" for the folder itself, else one or two \"..\" then plain names), as platforms/romfolders/.yml records it from the OS's own source (roms_root.from_bios). It is followed only from a BIOS folder named bios_folder (the platform's base_destination). Any other leaves root: roms entries out (no_roms_directory).", "type": "object", "additionalProperties": false, "required": [ @@ -577,7 +577,7 @@ ], "properties": { "bios_folder": { - "description": "Name of the BIOS folder; the other paths are relative to the folder holding it.", + "description": "Name of the BIOS folder. The other paths are relative to the folder holding it.", "type": "string", "minLength": 1, "pattern": "^[^/\\\\]+$" @@ -604,7 +604,7 @@ } }, "configs_directory": { - "description": "Where the frontend keeps its emulators' configs, present when an entry carries root: configs. from_bios is that folder relative to the BIOS folder, as the platform's own path code builds it (Batocera's batoceraPaths CONFIGS, ../system/configs), with the rules of roms_directory.from_bios. It is followed only from a BIOS folder named bios_folder; any other leaves root: configs entries out (no_configs_directory).", + "description": "Where the frontend keeps its emulators' configs, present when an entry carries root: configs. from_bios is that folder relative to the BIOS folder, as the platform's own path code builds it (Batocera's batoceraPaths CONFIGS, ../system/configs), with the rules of roms_directory.from_bios. It is followed only from a BIOS folder named bios_folder. Any other leaves root: configs entries out (no_configs_directory).", "type": "object", "additionalProperties": false, "required": [ diff --git a/scripts/launchers.py b/scripts/launchers.py index 0e19a267d..f8199f870 100644 --- a/scripts/launchers.py +++ b/scripts/launchers.py @@ -402,8 +402,8 @@ class HandedDirectory: # The placeholder a launcher record writes for the system it is launched -# for, as Recalbox's extractor spells it: Recalbox passes XRoar -rompath -# os.path.join(BIOS, args.system). +# for, as Recalbox's extractor spells it: Recalbox passes XRoar `-rompath` +# `os.path.join(BIOS, args.system)`. SYSTEM_PLACEHOLDER = "" @@ -772,7 +772,7 @@ def standalone_bindings( ``systems`` are the platform systems, by our ids, an entry is launched for. A record naming systems replaces, for those systems, what the - records naming none give: RetroBat passes XRoar -rompath bios/coco for + records naming none give: RetroBat passes XRoar `-rompath bios/coco` for coco and bios/dragon for every other system. A profile whose program reads its files beside its executable declares @@ -1003,7 +1003,7 @@ class LauncherLayer: names = own & basenames if names: return _Candidate(entry, names, False) - # RetroPie runs sdl2trs with -romfile4p $biosdir/level4p.rom: the + # RetroPie runs sdl2trs with `-romfile4p $biosdir/level4p.rom`: the # file under that name is the one the romfile4p slot reads. if set_by(entry, place.passes_to): return _Candidate(entry, basenames, False, by_setting_or_stem=True) diff --git a/tests/test_e2e.py b/tests/test_e2e.py index 770c22fe6..9c8379233 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -64,6 +64,13 @@ from verify import ( verify_platform, ) +try: + import jsonschema # noqa: F401 +except ImportError: + HAS_JSONSCHEMA = False +else: + HAS_JSONSCHEMA = True + def _h(data: bytes) -> dict: """Return sha1, md5, crc32 for test data.""" @@ -6995,6 +7002,7 @@ class OneOfASearchList(unittest.TestCase): self.assertTrue(holds_required_members(str(path), members)) self.assertFalse(holds_required_members(str(path), members[:1] + members[2:])) + @unittest.skipUnless(HAS_JSONSCHEMA, "validate_schemas needs jsonschema") def test_schema_refuses_a_set_split_on_required(self): from validate_schemas import _group_requirement_split, ROOT diff --git a/tests/test_launcher_layer.py b/tests/test_launcher_layer.py index 4257f5863..0e6e8fae0 100644 --- a/tests/test_launcher_layer.py +++ b/tests/test_launcher_layer.py @@ -253,7 +253,7 @@ class LauncherLayerTest(unittest.TestCase): self.assertNotIn("core.bin", self._pack_names()) def test_a_file_handed_to_an_entry_setting_takes_the_entry_content(self) -> None: - """RetroPie runs sdl2trs with -romfile4p $biosdir/level4p.rom and + """RetroPie runs sdl2trs with `-romfile4p $biosdir/level4p.rom` and EmuDeck writes a No-Intro name into ares's NeoGeoPocket firmware setting: neither name is the core's, and the held file of the entry that setting reads went in under no name at all.""" diff --git a/tests/test_launcher_paths.py b/tests/test_launcher_paths.py index eb2730746..a4e1565bd 100644 --- a/tests/test_launcher_paths.py +++ b/tests/test_launcher_paths.py @@ -277,7 +277,7 @@ class OnlyWhatABoardBuildsIsRun(unittest.TestCase): def test_only_what_a_board_builds_or_the_user_supplies_is_run(self): from scraper import batocera_scraper as bs - # A board selects kodi's package and buildroot's mpv; citron is + # A board selects kodi's package and buildroot's mpv. Citron is # listed by es_systems, so the symbol of a folder of that name does # not count for it. built = bs.boards_build( diff --git a/tests/test_standalone_links.py b/tests/test_standalone_links.py index 4bdfb6935..9da2ac5f0 100644 --- a/tests/test_standalone_links.py +++ b/tests/test_standalone_links.py @@ -257,7 +257,7 @@ class PerSystem(unittest.TestCase): ) def test_a_system_placeholder_gives_one_directory_per_system(self) -> None: - # Recalbox passes -rompath os.path.join(BIOS, args.system). + # Recalbox passes `-rompath os.path.join(BIOS, args.system)`. cfg = { **config(passes("emu", "/recalbox/share/bios/", "command line -rompath")), "systems": { diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index 492f21ee9..2c1f6a867 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -55,7 +55,7 @@ Every module in `tests/`, by the area it guards. |------|----------|---------| | Core regression suites | synthetic | `test_e2e`, `test_audit_regressions`, `test_tool_messages` | | File resolution and identity | synthetic + collection | `test_adler32_resolution`, `test_agnostic_scan`, `test_agnostic_scan_mode`, `test_agnostic_scope`, `test_agnostic_substitute`, `test_archive_build`, `test_archive_choice`, `test_archive_recipes`, `test_bios_companions`, `test_bundled_identity`, `test_casefold_cache`, `test_db_aliases`, `test_declared_names`, `test_derivations`, `test_directory_entries`, `test_extra_identity`, `test_foreign_tree`, `test_foreign_tree_names`, `test_frontend_digest`, `test_game_data_homonyms`, `test_hashing`, `test_identity`, `test_maker_folder_names`, `test_md5_pack_context`, `test_name_tiebreak`, `test_nec_filing`, `test_np2_fonts`, `test_owner_resolution`, `test_path_suffix`, `test_proven_name`, `test_resolution_evidence`, `test_sega_filing`, `test_shipped_content`, `test_twin_declaration_status`, `test_twin_declarations`, `test_validated_choice`, `test_variant_choice`, `test_variant_identity`, `test_zipped_member` | -| Verification, cross-reference and profile checks | synthetic + profiles | `test_bios_layout`, `test_cache_independent_counts`, `test_case_insensitive_platforms`, `test_checks_have_values`, `test_core_file_fills_declaration`, `test_core_size_refusal`, `test_cross_reference_sources`, `test_crypto_aes`, `test_crypto_unique_data`, `test_crypto_verify`, `test_entry_categories`, `test_file_consumers`, `test_fileless_audit`, `test_flycast_layout`, `test_gap_settlement`, `test_hidden_extras`, `test_launcher_aliases`, `test_launcher_layer`, `test_native_mode`, `test_no_case_collisions`, `test_path_conflict_status`, `test_path_null_copies`, `test_profile_checks`, `test_profile_names`, `test_profile_paths`, `test_profile_repositories`, `test_romm_hash_rule`, `test_shared_groups`, `test_standalone_links`, `test_standalone_mode`, `test_undeclared_dedup`, `test_unplaced_extras`, `test_validation_scope`, `test_verify_all_dir`, `test_verify_data_dir_notice`, `test_verify_emulator_evidence`, `test_verify_listing`, `test_verify_pack_choice`, `test_verify_release_asset` | +| Verification, cross-reference and profile checks | synthetic + profiles | `test_bios_layout`, `test_cache_independent_counts`, `test_case_insensitive_platforms`, `test_checks_have_values`, `test_content_declarations`, `test_core_file_fills_declaration`, `test_core_size_refusal`, `test_cross_reference_sources`, `test_crypto_aes`, `test_crypto_unique_data`, `test_crypto_verify`, `test_entry_categories`, `test_file_consumers`, `test_fileless_audit`, `test_flycast_layout`, `test_gap_settlement`, `test_hidden_extras`, `test_launcher_aliases`, `test_launcher_gaps`, `test_launcher_layer`, `test_launcher_paths`, `test_launcher_placement`, `test_native_mode`, `test_no_case_collisions`, `test_path_conflict_status`, `test_path_null_copies`, `test_profile_checks`, `test_profile_names`, `test_profile_paths`, `test_profile_repositories`, `test_romm_hash_rule`, `test_shared_groups`, `test_standalone_links`, `test_standalone_mode`, `test_undeclared_dedup`, `test_unplaced_extras`, `test_validation_scope`, `test_verify_all_dir`, `test_verify_data_dir_notice`, `test_verify_emulator_evidence`, `test_verify_listing`, `test_verify_pack_choice`, `test_verify_release_asset` | | Packs and manifests | synthetic + real packs | `test_artifact_lock`, `test_atomic_artifacts`, `test_batocera_cores`, `test_core_data_trees`, `test_deterministic_zip`, `test_emulator_pack_counts`, `test_finalize_pack_names`, `test_large_file_cache`, `test_libretro_layout`, `test_manifest_entry`, `test_minui_platform`, `test_no_game_data`, `test_pack_conformance_dirs`, `test_pack_core_extras`, `test_pack_counts`, `test_pack_destinations`, `test_pack_failures`, `test_pack_file_names`, `test_pack_grouping`, `test_pack_integrity`, `test_pack_member_identity`, `test_pack_readme`, `test_platform_inheritance`, `test_platform_keys`, `test_release_packs`, `test_required_only_archives`, `test_required_only_twins`, `test_save_root`, `test_split_extras`, `test_split_names`, `test_split_pack`, `test_system_pack_extras`, `test_system_pack_parity`, `test_torrentzip`, `test_which_pack` | | Slots and regions | synthetic + profiles | `test_region`, `test_region_agnostic_scan`, `test_region_groups`, `test_region_held`, `test_region_platform_index`, `test_region_required_only`, `test_slot`, `test_slots`, `test_slots_shipped_file` | | Hardware targets and systems | synthetic + target files | `test_amberelec_targets`, `test_emuelec_targets`, `test_extract_targets`, `test_lakka_targets`, `test_libretro_slugs`, `test_system_ids`, `test_system_selection`, `test_target_names`, `test_target_scrapers`, `test_target_systems`, `test_target_verify` | @@ -69,7 +69,7 @@ Every module in `tests/`, by the area it guards. | Pack probe | the cores a platform runs for a system, staging grown to a fixed point, runs mapped to pack and manifest entries, the line and its schema, the probe lock per run, that no page shows its lines. One class builds a real pack narrowed to PlayStation and lays it out both ways, from the archive and with `install.py --dest`, and skips without the collection | `test_pack_probe` | | Automation runner, disk cleanup, asset sync and bit-flip repair | fake jobs, git repositories with a bare origin and session worktrees, a committed file with one bit flipped, canned CI runs in place of `gh` | `test_autorun` | | Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` | -| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_cited_lines`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_pin_build`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_retired_files`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_agents`, `test_site_arcade_boards`, `test_site_availability`, `test_site_bootstrap`, `test_site_built_gate`, `test_site_components`, `test_site_console_folders`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_css`, `test_site_demo`, `test_site_density_cards`, `test_site_emulator_page`, `test_site_emulators`, `test_site_entry`, `test_site_entry_tables`, `test_site_exports`, `test_site_freshness`, `test_site_funnel`, `test_site_images`, `test_site_inputs`, `test_site_insights`, `test_site_integration`, `test_site_nav`, `test_site_picker`, `test_site_platform_density`, `test_site_platform_hashes`, `test_site_privacy`, `test_site_pronouns`, `test_site_reference`, `test_site_render`, `test_site_search`, `test_site_steps`, `test_site_system_links`, `test_site_system_names`, `test_site_system_sections`, `test_site_systems`, `test_site_validation`, `test_site_weight`, `test_site_wiki`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_version_basis`, `test_workflow_paths` | +| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_cited_lines`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_amberelec`, `test_launchers_emudeck`, `test_launchers_emuelec`, `test_launchers_knulli`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_pin_build`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retired_files`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_agents`, `test_site_arcade_boards`, `test_site_availability`, `test_site_bootstrap`, `test_site_built_gate`, `test_site_components`, `test_site_console_folders`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_css`, `test_site_demo`, `test_site_density_cards`, `test_site_emulator_page`, `test_site_emulators`, `test_site_entry`, `test_site_entry_tables`, `test_site_exports`, `test_site_freshness`, `test_site_funnel`, `test_site_images`, `test_site_inputs`, `test_site_insights`, `test_site_integration`, `test_site_nav`, `test_site_picker`, `test_site_platform_density`, `test_site_platform_hashes`, `test_site_privacy`, `test_site_pronouns`, `test_site_reference`, `test_site_render`, `test_site_search`, `test_site_steps`, `test_site_system_links`, `test_site_system_names`, `test_site_system_sections`, `test_site_systems`, `test_site_validation`, `test_site_weight`, `test_site_wiki`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_version_basis`, `test_workflow_paths` | | Games on the card | small DATs, listxml and setup sources, a fake fetcher, fixture install lists, and the committed data against its schemas | `test_arcade_parents`, `test_game_catalogs`, `test_game_headers`, `test_game_regions`, `test_games_index`, `test_romfolders_card`, `test_romfolders_es` | ## Test architecture