feat: smaller cells (10x10px) for higher detail

Cells reduced from 16x16 to 10x10 pixels. Same window size now
shows more of the world — finer detail, more cells visible.
Adaptive viewport recalculates grid_w/grid_h from new cell size.

109 tests, 0 failures
This commit is contained in:
Emil
2026-06-21 10:04:21 +03:00
parent 8b055ac37f
commit 285688bcad
70 changed files with 22764 additions and 624 deletions
+1
View File
@@ -0,0 +1 @@
{"0": "Vulkan Core Setup", "1": "Terminal Renderer & Game Loop", "2": "Graphics Vulkan Renderer", "3": "AI Pipe Protocol", "4": "AI Game Session", "5": "CLI Entry & GPU Mode", "6": "AI State Export", "7": "Terminal Input Handler", "8": "Entity System", "9": "Cellular Automaton", "10": "Replay System", "11": "Edge Case Tests", "12": "AI Actions & Grid", "13": "Material Physics Tests", "14": "Player Controller", "15": "Cell & Material Types", "16": "Material Interaction Tests", "17": "Player Control Tests", "18": "AI Agent Roadmap", "19": "Verlet Physics Solver", "20": "Render & Phase Roadmap", "21": "Collision Robustness Tests", "22": "Render Mode & UI Roadmap", "23": "Window Input (winit)", "24": "Determinism Tests", "25": "Ragdoll Death Tests", "26": "Grid Collision Resolver", "27": "Material Registry", "28": "Entity Types & Game Loop", "29": "Adaptive Viewport", "30": "Headless Dump & Combat Plan", "31": "Entity Damage Tests", "32": "Entity Movement Tests", "33": "Lava Physics Tests", "34": "Sand Physics Tests", "35": "Acid Physics Tests", "36": "Water Physics Tests", "37": "Ragdoll Physics Plan", "38": "Integration Tests", "39": "Per-Cell Color Plan", "40": "Game Run & Input", "41": "Slope Stepping", "42": "Renderer Trait", "43": "AI Module Root", "44": "Physics Module Root", "45": "Library Root", "46": "World Module Root"}
+1
View File
@@ -0,0 +1 @@
/home/emil/.local/share/uv/tools/graphifyy/bin/python
+1
View File
@@ -0,0 +1 @@
/home/emil/Desktop/Verbatim
+285
View File
@@ -0,0 +1,285 @@
# Graph Report - . (2026-06-21)
## Corpus Check
- Corpus is ~33,231 words - fits in a single context window. You may not need a graph.
## Summary
- 690 nodes · 1218 edges · 47 communities (44 shown, 3 thin omitted)
- Extraction: 97% EXTRACTED · 3% INFERRED · 0% AMBIGUOUS · INFERRED: 32 edges (avg confidence: 0.86)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Vulkan Core Setup|Vulkan Core Setup]]
- [[_COMMUNITY_Terminal Renderer & Game Loop|Terminal Renderer & Game Loop]]
- [[_COMMUNITY_Graphics Vulkan Renderer|Graphics Vulkan Renderer]]
- [[_COMMUNITY_AI Pipe Protocol|AI Pipe Protocol]]
- [[_COMMUNITY_AI Game Session|AI Game Session]]
- [[_COMMUNITY_CLI Entry & GPU Mode|CLI Entry & GPU Mode]]
- [[_COMMUNITY_AI State Export|AI State Export]]
- [[_COMMUNITY_Terminal Input Handler|Terminal Input Handler]]
- [[_COMMUNITY_Entity System|Entity System]]
- [[_COMMUNITY_Cellular Automaton|Cellular Automaton]]
- [[_COMMUNITY_Replay System|Replay System]]
- [[_COMMUNITY_Edge Case Tests|Edge Case Tests]]
- [[_COMMUNITY_AI Actions & Grid|AI Actions & Grid]]
- [[_COMMUNITY_Material Physics Tests|Material Physics Tests]]
- [[_COMMUNITY_Player Controller|Player Controller]]
- [[_COMMUNITY_Cell & Material Types|Cell & Material Types]]
- [[_COMMUNITY_Material Interaction Tests|Material Interaction Tests]]
- [[_COMMUNITY_Player Control Tests|Player Control Tests]]
- [[_COMMUNITY_AI Agent Roadmap|AI Agent Roadmap]]
- [[_COMMUNITY_Verlet Physics Solver|Verlet Physics Solver]]
- [[_COMMUNITY_Render & Phase Roadmap|Render & Phase Roadmap]]
- [[_COMMUNITY_Collision Robustness Tests|Collision Robustness Tests]]
- [[_COMMUNITY_Render Mode & UI Roadmap|Render Mode & UI Roadmap]]
- [[_COMMUNITY_Window Input (winit)|Window Input (winit)]]
- [[_COMMUNITY_Determinism Tests|Determinism Tests]]
- [[_COMMUNITY_Ragdoll Death Tests|Ragdoll Death Tests]]
- [[_COMMUNITY_Grid Collision Resolver|Grid Collision Resolver]]
- [[_COMMUNITY_Material Registry|Material Registry]]
- [[_COMMUNITY_Entity Types & Game Loop|Entity Types & Game Loop]]
- [[_COMMUNITY_Adaptive Viewport|Adaptive Viewport]]
- [[_COMMUNITY_Headless Dump & Combat Plan|Headless Dump & Combat Plan]]
- [[_COMMUNITY_Entity Damage Tests|Entity Damage Tests]]
- [[_COMMUNITY_Entity Movement Tests|Entity Movement Tests]]
- [[_COMMUNITY_Lava Physics Tests|Lava Physics Tests]]
- [[_COMMUNITY_Sand Physics Tests|Sand Physics Tests]]
- [[_COMMUNITY_Acid Physics Tests|Acid Physics Tests]]
- [[_COMMUNITY_Water Physics Tests|Water Physics Tests]]
- [[_COMMUNITY_Ragdoll Physics Plan|Ragdoll Physics Plan]]
- [[_COMMUNITY_Per-Cell Color Plan|Per-Cell Color Plan]]
- [[_COMMUNITY_Game Run & Input|Game Run & Input]]
- [[_COMMUNITY_Slope Stepping|Slope Stepping]]
- [[_COMMUNITY_Renderer Trait|Renderer Trait]]
## God Nodes (most connected - your core abstractions)
1. `VulkanRenderer` - 37 edges
2. `GraphicsRenderer` - 31 edges
3. `GameSession` - 26 edges
4. `Game` - 24 edges
5. `Entity` - 20 edges
6. `CellularAutomaton` - 19 edges
7. `InputHandler` - 18 edges
8. `Result` - 17 edges
9. `String` - 17 edges
10. `setup()` - 17 edges
## Surprising Connections (you probably didn't know these)
- `pipe render mode` --semantically_similar_to--> `AI pipe protocol (JSON stdin/stdout)` [INFERRED] [semantically similar]
AGENTS.md → PLAN.md
- `Game::fixed_update()` --semantically_similar_to--> `Fixed 60Hz timestep` [INFERRED] [semantically similar]
AGENTS.md → PLAN.md
- `Player` --semantically_similar_to--> `Player entity (headless dump)` [INFERRED] [semantically similar]
AGENTS.md → headless_dump.txt
- `rigid entity type` --semantically_similar_to--> `Rigid entities (AABB + slope stepping)` [INFERRED] [semantically similar]
AGENTS.md → PLAN.md
- `ragdoll entity type` --semantically_similar_to--> `Ragdoll corpses (Verlet)` [INFERRED] [semantically similar]
AGENTS.md → PLAN.md
## Import Cycles
- 1-file cycle: `src/ai/action.rs -> src/ai/action.rs`
- 1-file cycle: `src/world/grid.rs -> src/world/grid.rs`
- 1-file cycle: `src/ai/protocol.rs -> src/ai/protocol.rs`
- 1-file cycle: `src/ai/replay.rs -> src/ai/replay.rs`
- 1-file cycle: `src/ai/scenario.rs -> src/ai/scenario.rs`
- 1-file cycle: `src/ai/session.rs -> src/ai/session.rs`
- 1-file cycle: `src/ai/state.rs -> src/ai/state.rs`
- 1-file cycle: `src/game.rs -> src/game.rs`
- 1-file cycle: `src/entity/player.rs -> src/entity/player.rs`
- 1-file cycle: `src/render/terminal.rs -> src/render/terminal.rs`
- 1-file cycle: `src/input.rs -> src/input.rs`
- 1-file cycle: `src/main.rs -> src/main.rs`
- 1-file cycle: `src/physics/collision.rs -> src/physics/collision.rs`
- 1-file cycle: `src/physics/verlet.rs -> src/physics/verlet.rs`
- 1-file cycle: `src/render/graphics.rs -> src/render/graphics.rs`
- 1-file cycle: `src/render/vulkan.rs -> src/render/vulkan.rs`
- 1-file cycle: `src/render/window_input.rs -> src/render/window_input.rs`
- 1-file cycle: `src/world/cellular.rs -> src/world/cellular.rs`
- 1-file cycle: `src/world/material.rs -> src/world/material.rs`
- 1-file cycle: `tests/collision_robust.rs -> tests/collision_robust.rs`
## Hyperedges (group relationships)
- **Game loop fixed_update pipeline: CA step → rigid update → ragdoll update → damage** — [INFERRED]
- **Three render modes read the same Grid + EntityManager (single source of truth)** — [INFERRED]
- **GpuRenderer trait unifies VulkanRenderer + GraphicsRenderer behind run_gpu_mode<R>() with identical event loops** — [INFERRED]
## Communities (47 total, 3 thin omitted)
### Community 0 - "Vulkan Core Setup"
Cohesion: 0.10
Nodes (61): BufferUsageFlags, c_char, DescriptorPool, DescriptorSet, DescriptorSetLayout, Format, Image, MemoryPropertyFlags (+53 more)
### Community 1 - "Terminal Renderer & Game Loop"
Cohesion: 0.07
Nodes (19): CellularAutomaton, Duration, InputHandler, Player, R, TerminalRenderer, Renderer, Game (+11 more)
### Community 2 - "Graphics Vulkan Renderer"
Cohesion: 0.07
Nodes (31): ColorInstance, GraphicsRenderer, PushConstants, Arc, Buffer, CommandBuffer, CommandPool, Device (+23 more)
### Community 3 - "AI Pipe Protocol"
Cohesion: 0.12
Nodes (31): Command, handle_command(), Response, run_pipe_protocol(), Assertion, AssertionResult, check_assertion(), format_results() (+23 more)
### Community 4 - "AI Game Session"
Cohesion: 0.09
Nodes (13): GameSession, Default, ReplayRecorder, AiAction, CellInfo, EntityInfo, Game, GameState (+5 more)
### Community 5 - "CLI Entry & GPU Mode"
Cohesion: 0.15
Nodes (20): Cli, dump_view(), GpuRenderer, main(), player_info(), Arc, EntityManager, Game (+12 more)
### Community 6 - "AI State Export"
Cohesion: 0.13
Nodes (23): build_game_state(), CellInfo, entity_info(), entity_kind_name(), EntityInfo, GameState, material_from_name(), parse_entity_kind() (+15 more)
### Community 7 - "Terminal Input Handler"
Cohesion: 0.13
Nodes (15): Event, JoinHandle, Receiver, Action, HeldKey, HeldState, InputHandler, MaterialBrush (+7 more)
### Community 8 - "Entity System"
Cohesion: 0.15
Nodes (9): Constraint, Entity, EntityKind, EntityManager, EntityId, Option, Self, SubBody (+1 more)
### Community 9 - "Cellular Automaton"
Cohesion: 0.29
Nodes (4): Grid, MaterialId, Self, CellularAutomaton
### Community 10 - "Replay System"
Cohesion: 0.13
Nodes (11): ReplayEvent, ReplayPlayer, ReplayRecorder, ReplayRecording, ReplayEvent, AiAction, GameSession, Result (+3 more)
### Community 11 - "Edge Case Tests"
Cohesion: 0.16
Nodes (17): center_camera_on_player(), clear_region_removes_all_materials(), entity_at_right_boundary(), entity_at_world_boundary_stays_in_bounds(), fill_rect_creates_correct_material_count(), find_material_locates_existing(), find_material_returns_none_for_absent(), goblin_has_correct_max_health() (+9 more)
### Community 12 - "AI Actions & Grid"
Cohesion: 0.18
Nodes (7): AiAction, Cell, Game, MaterialId, Self, Vec, Grid
### Community 13 - "Material Physics Tests"
Cohesion: 0.22
Nodes (17): bone_is_static(), dirt_is_solid(), fire_dies_over_time(), fire_ignites_flesh(), fire_ignites_grass(), fire_ignites_wood(), grass_is_solid(), lava_initial_temp_is_high() (+9 more)
### Community 14 - "Player Controller"
Cohesion: 0.21
Nodes (6): Player, Entity, EntityId, EntityManager, Option, Self
### Community 15 - "Cell & Material Types"
Cohesion: 0.17
Nodes (4): Self, Cell, MaterialId, rand_u8()
### Community 16 - "Material Interaction Tests"
Cohesion: 0.26
Nodes (14): acid_dissolves_dirt(), acid_dissolves_grass(), acid_does_not_dissolve_empty(), fire_does_not_ignite_stone(), fire_does_not_ignite_water(), fire_spreads_through_wood_line(), lava_and_water_produce_both_steam_and_stone(), lava_cools_to_stone_eventually() (+6 more)
### Community 17 - "Player Control Tests"
Cohesion: 0.26
Nodes (14): continuous_movement_does_not_fall_through_floor(), jump_goes_up_then_falls_back(), jump_while_airborne_does_nothing(), move_left_changes_x_position(), move_left_then_right_cancels(), move_right_changes_x_position(), player_health_stays_full_without_damage(), player_not_on_ground_while_jumping() (+6 more)
### Community 18 - "AI Agent Roadmap"
Cohesion: 0.18
Nodes (14): AiAction, GameSession, pipe render mode, AI pipe protocol (JSON stdin/stdout), Seeded RNG determinism, LLM agent (Ollama/Llama/Qwen), Phase 7: AI Agent Integration, Phase 8: Web Arena & Training Pipeline (+6 more)
### Community 19 - "Verlet Physics Solver"
Cohesion: 0.22
Nodes (5): Constraint, SubBody, VerletSolver, MaterialId, Self
### Community 20 - "Render & Phase Roadmap"
Cohesion: 0.15
Nodes (11): Chunk system (64x64), Cross-platform support (Win/Linux/macOS), Multi-layer world architecture, Phase 2: World & Exploration, Phase 3: RPG Layer, Phase 4: Render Modes, Phase 4b: Graphics layers (lighting/particles/textures), Phase 5: Content & Polish (+3 more)
### Community 21 - "Collision Robustness Tests"
Cohesion: 0.27
Nodes (10): entity_collision_with_dirt_wall(), player_blocked_by_ceiling(), player_blocked_by_left_wall(), player_blocked_by_right_wall(), player_blocked_by_two_walls_both_sides(), player_does_not_stick_to_wall(), player_slides_along_wall(), player_squeezes_through_gap() (+2 more)
### Community 22 - "Render Mode & UI Roadmap"
Cohesion: 0.22
Nodes (11): EntityManager, Grid (250x250) source of truth, ascii render mode, graphics render mode, terminal render mode, ASCII renderer (Vulkan glyph atlas), Graphics renderer (Vulkan colored cells), Phase 1.5: UI Layer (+3 more)
### Community 23 - "Window Input (winit)"
Cohesion: 0.20
Nodes (7): ElementState, HashSet, PhysicalKey, WindowInput, KeyCode, Option, Self
### Community 24 - "Determinism Tests"
Cohesion: 0.22
Nodes (3): determinism_with_spawn_and_damage(), GameSession, setup()
### Community 25 - "Ragdoll Death Tests"
Cohesion: 0.38
Nodes (9): corpse_exists_in_world(), damage_reduces_health_progressively(), death_transitions_rigid_to_ragdoll(), player_death_becomes_corpse(), ragdoll_bodies_stay_near_each_other(), ragdoll_falls_after_death(), GameSession, setup() (+1 more)
### Community 26 - "Grid Collision Resolver"
Cohesion: 0.46
Nodes (6): apply_liquid_drag(), CollisionResult, resolve_grid_collision(), Grid, Self, SubBody
### Community 27 - "Material Registry"
Cohesion: 0.43
Nodes (4): MaterialId, Self, Material, MaterialRegistry
### Community 28 - "Entity Types & Game Loop"
Cohesion: 0.29
Nodes (7): cellular entity type, CellularAutomaton, Game::fixed_update(), rigid entity type, Fixed 60Hz timestep, 14 CA Materials, Rigid entities (AABB + slope stepping)
### Community 29 - "Adaptive Viewport"
Cohesion: 0.33
Nodes (7): check_resize(), GpuRenderer trait, GraphicsRenderer, run_gpu_mode<R>(), VulkanRenderer, WindowInput, Adaptive viewport
### Community 30 - "Headless Dump & Combat Plan"
Cohesion: 0.33
Nodes (6): Player, vector movement convention, Goblin entity (headless dump), Grid ASCII rendering (headless dump), Player entity (headless dump), Phase 1: Combat & Interaction
### Community 31 - "Entity Damage Tests"
Cohesion: 0.52
Nodes (6): entity_blocked_by_stone(), entity_dies_becomes_corpse(), entity_on_fire_takes_damage_over_time(), entity_takes_lava_damage(), GameSession, setup_empty()
### Community 32 - "Entity Movement Tests"
Cohesion: 0.48
Nodes (5): player_blocked_by_stone_wall(), player_can_move_right(), player_survives_fall(), GameSession, setup_empty()
### Community 33 - "Lava Physics Tests"
Cohesion: 0.52
Nodes (6): lava_flows_down(), lava_ignites_grass(), lava_ignites_wood(), lava_plus_water_makes_steam(), GameSession, setup_empty()
### Community 34 - "Sand Physics Tests"
Cohesion: 0.52
Nodes (6): GameSession, sand_displaces_water(), sand_does_not_fall_through_stone(), sand_falls_down(), sand_piles_on_stone(), setup_empty()
### Community 35 - "Acid Physics Tests"
Cohesion: 0.60
Nodes (5): acid_dissolves_wood(), acid_does_not_dissolve_stone(), acid_flows_down(), GameSession, setup_empty()
### Community 36 - "Water Physics Tests"
Cohesion: 0.60
Nodes (5): GameSession, setup_empty(), water_does_not_pass_through_stone_wall(), water_flows_down(), water_spreads_sideways()
### Community 37 - "Ragdoll Physics Plan"
Cohesion: 0.50
Nodes (5): Constraint, ragdoll entity type, SubBody, VerletSolver, Ragdoll corpses (Verlet)
### Community 39 - "Per-Cell Color Plan"
Cohesion: 0.67
Nodes (4): Cell struct, inline cell colors convention, MaterialRegistry, Per-cell color (reality layer)
## Knowledge Gaps
- **106 isolated node(s):** `String`, `CellInfo`, `Vec`, `EntityInfo`, `ScenarioResult` (+101 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **3 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `Cell` connect `AI Actions & Grid` to `Cellular Automaton`?**
_High betweenness centrality (0.011) - this node is a cross-community bridge._
- **Why does `GameSession` connect `AI Game Session` to `AI State Export`?**
_High betweenness centrality (0.011) - this node is a cross-community bridge._
- **What connects `String`, `CellInfo`, `Vec` to the rest of the system?**
_108 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Vulkan Core Setup` be split into smaller, more focused modules?**
_Cohesion score 0.09525899912203688 - nodes in this community are weakly interconnected._
- **Should `Terminal Renderer & Game Loop` be split into smaller, more focused modules?**
_Cohesion score 0.06956521739130435 - nodes in this community are weakly interconnected._
- **Should `Graphics Vulkan Renderer` be split into smaller, more focused modules?**
_Cohesion score 0.06543385490753911 - nodes in this community are weakly interconnected._
- **Should `AI Pipe Protocol` be split into smaller, more focused modules?**
_Cohesion score 0.12063492063492064 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_entity_mod_rs", "label": "mod.rs", "file_type": "code", "source_file": "src/entity/mod.rs", "source_location": "L1"}], "edges": [{"source": "home_emil_desktop_verbatim_src_entity_mod_rs", "target": "entity", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/entity/mod.rs", "source_location": "L4", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_entity_mod_rs", "target": "player", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/entity/mod.rs", "source_location": "L5", "weight": 1.0, "context": "import"}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_lib_rs", "label": "lib.rs", "file_type": "code", "source_file": "src/lib.rs", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_physics_mod_rs", "label": "mod.rs", "file_type": "code", "source_file": "src/physics/mod.rs", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_world_material_rs", "label": "material.rs", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L1"}, {"id": "world_material_material", "label": "Material", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L4"}, {"id": "materialid", "label": "MaterialId", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L5"}, {"id": "world_material_materialregistry", "label": "MaterialRegistry", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L24"}, {"id": "world_material_materialregistry_new", "label": ".new()", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L29"}, {"id": "self", "label": "Self", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L29"}, {"id": "world_material_materialregistry_get", "label": ".get()", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L260"}, {"id": "world_material_materialregistry_instance", "label": ".instance()", "file_type": "code", "source_file": "src/world/material.rs", "source_location": "L264"}], "edges": [{"source": "home_emil_desktop_verbatim_src_world_material_rs", "target": "materialid", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L1", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_world_material_rs", "target": "world_material_material", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L4", "weight": 1.0}, {"source": "world_material_material", "target": "materialid", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L5", "weight": 1.0, "context": "field"}, {"source": "home_emil_desktop_verbatim_src_world_material_rs", "target": "world_material_materialregistry", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L24", "weight": 1.0}, {"source": "world_material_materialregistry", "target": "world_material_material", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L25", "weight": 1.0, "context": "field"}, {"source": "world_material_materialregistry", "target": "world_material_materialregistry_new", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L29", "weight": 1.0}, {"source": "world_material_materialregistry_new", "target": "self", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L29", "weight": 1.0, "context": "return_type"}, {"source": "world_material_materialregistry", "target": "world_material_materialregistry_get", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L260", "weight": 1.0}, {"source": "world_material_materialregistry_get", "target": "materialid", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L260", "weight": 1.0, "context": "parameter_type"}, {"source": "world_material_materialregistry_get", "target": "world_material_material", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L260", "weight": 1.0, "context": "return_type"}, {"source": "world_material_materialregistry", "target": "world_material_materialregistry_instance", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L264", "weight": 1.0}, {"source": "world_material_materialregistry_instance", "target": "world_material_materialregistry", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L264", "weight": 1.0, "context": "return_type"}, {"source": "world_material_materialregistry_instance", "target": "world_material_materialregistry_new", "relation": "calls", "context": "call", "confidence": "EXTRACTED", "source_file": "src/world/material.rs", "source_location": "L265", "weight": 1.0}], "raw_calls": [{"caller_nid": "world_material_materialregistry_instance", "callee": "get_or_init", "is_member_call": true, "source_file": "/home/emil/Desktop/Verbatim/src/world/material.rs", "source_location": "L266"}]}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_world_mod_rs", "label": "mod.rs", "file_type": "code", "source_file": "src/world/mod.rs", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_render_window_input_rs", "label": "window_input.rs", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L1"}, {"id": "render_window_input_windowinput", "label": "WindowInput", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L4"}, {"id": "option", "label": "Option", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L13"}, {"id": "hashset", "label": "HashSet", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L15"}, {"id": "keycode", "label": "KeyCode", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L15"}, {"id": "render_window_input_windowinput_new", "label": ".new()", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L19"}, {"id": "self", "label": "Self", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L19"}, {"id": "render_window_input_windowinput_on_key_event", "label": ".on_key_event()", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L29"}, {"id": "physicalkey", "label": "PhysicalKey", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L29"}, {"id": "elementstate", "label": "ElementState", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L29"}, {"id": "render_window_input_windowinput_update", "label": ".update()", "file_type": "code", "source_file": "src/render/window_input.rs", "source_location": "L45"}], "edges": [{"source": "home_emil_desktop_verbatim_src_render_window_input_rs", "target": "hashset", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L1", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_render_window_input_rs", "target": "keyboard", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L2", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_render_window_input_rs", "target": "render_window_input_windowinput", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L4", "weight": 1.0}, {"source": "render_window_input_windowinput", "target": "option", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L13", "weight": 1.0, "context": "field"}, {"source": "render_window_input_windowinput", "target": "hashset", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L15", "weight": 1.0, "context": "field"}, {"source": "render_window_input_windowinput", "target": "keycode", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L15", "weight": 1.0, "context": "generic_arg"}, {"source": "render_window_input_windowinput", "target": "render_window_input_windowinput_new", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L19", "weight": 1.0}, {"source": "render_window_input_windowinput_new", "target": "self", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L19", "weight": 1.0, "context": "return_type"}, {"source": "render_window_input_windowinput", "target": "render_window_input_windowinput_on_key_event", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L29", "weight": 1.0}, {"source": "render_window_input_windowinput_on_key_event", "target": "physicalkey", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L29", "weight": 1.0, "context": "parameter_type"}, {"source": "render_window_input_windowinput_on_key_event", "target": "elementstate", "relation": "references", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L29", "weight": 1.0, "context": "parameter_type"}, {"source": "render_window_input_windowinput", "target": "render_window_input_windowinput_update", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/render/window_input.rs", "source_location": "L45", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_render_mod_rs", "label": "mod.rs", "file_type": "code", "source_file": "src/render/mod.rs", "source_location": "L1"}, {"id": "render_mod_renderer", "label": "Renderer", "file_type": "code", "source_file": "src/render/mod.rs", "source_location": "L9"}], "edges": [{"source": "home_emil_desktop_verbatim_src_render_mod_rs", "target": "entitymanager", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/render/mod.rs", "source_location": "L6", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_render_mod_rs", "target": "grid", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/render/mod.rs", "source_location": "L7", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_render_mod_rs", "target": "render_mod_renderer", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src/render/mod.rs", "source_location": "L9", "weight": 1.0}], "raw_calls": []}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "home_emil_desktop_verbatim_src_ai_mod_rs", "label": "mod.rs", "file_type": "code", "source_file": "src/ai/mod.rs", "source_location": "L1"}], "edges": [{"source": "home_emil_desktop_verbatim_src_ai_mod_rs", "target": "state", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/ai/mod.rs", "source_location": "L8", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_ai_mod_rs", "target": "aiaction", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/ai/mod.rs", "source_location": "L9", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_ai_mod_rs", "target": "gamesession", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/ai/mod.rs", "source_location": "L10", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_ai_mod_rs", "target": "replay", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/ai/mod.rs", "source_location": "L11", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_ai_mod_rs", "target": "scenario", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/ai/mod.rs", "source_location": "L12", "weight": 1.0, "context": "import"}, {"source": "home_emil_desktop_verbatim_src_ai_mod_rs", "target": "run_pipe_protocol", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "src/ai/mod.rs", "source_location": "L13", "weight": 1.0, "context": "import"}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (not a config/manifest)"}
@@ -0,0 +1 @@
{"nodes": [{"id": "headless_dump_headless_dump", "label": "headless_dump.txt", "file_type": "document", "source_file": "headless_dump.txt", "source_location": null, "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "headless_dump_player", "label": "Player entity (headless dump)", "file_type": "concept", "source_file": "headless_dump.txt", "source_location": "line 33", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "headless_dump_goblin", "label": "Goblin entity (headless dump)", "file_type": "concept", "source_file": "headless_dump.txt", "source_location": "line 184", "source_url": null, "captured_at": null, "author": null, "contributor": null}, {"id": "headless_dump_grid_render", "label": "Grid ASCII rendering (headless dump)", "file_type": "concept", "source_file": "headless_dump.txt", "source_location": "line 7", "source_url": null, "captured_at": null, "author": null, "contributor": null}], "edges": [{"source": "headless_dump_headless_dump", "target": "headless_dump_player", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "headless_dump.txt", "source_location": "line 33", "weight": 1.0}, {"source": "headless_dump_headless_dump", "target": "headless_dump_goblin", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "headless_dump.txt", "source_location": "line 184", "weight": 1.0}, {"source": "headless_dump_headless_dump", "target": "headless_dump_grid_render", "relation": "references", "confidence": "EXTRACTED", "confidence_score": 1.0, "source_file": "headless_dump.txt", "source_location": "line 7", "weight": 1.0}, {"source": "headless_dump_player", "target": "headless_dump_goblin", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "headless_dump.txt", "source_location": "line 184", "weight": 1.0}, {"source": "headless_dump_grid_render", "target": "agents_render_terminal", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.85, "source_file": "headless_dump.txt", "source_location": "line 7", "weight": 1.0}, {"source": "headless_dump_goblin", "target": "plan_phase1_combat", "relation": "conceptually_related_to", "confidence": "INFERRED", "confidence_score": 0.75, "source_file": "headless_dump.txt", "source_location": "line 184", "weight": 1.0}], "hyperedges": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
{
"runs": [
{
"date": "2026-06-21T06:52:56.684475+00:00",
"input_tokens": 0,
"output_tokens": 0,
"files": 58
}
],
"total_input_tokens": 0,
"total_output_tokens": 0
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+292
View File
@@ -0,0 +1,292 @@
{
"/home/emil/Desktop/Verbatim/scenarios/acid_dissolves_wood.json": {
"mtime": 1781970764.6442819,
"ast_hash": "8d56cc1848be6548decbe1e329491b4e",
"semantic_hash": "8d56cc1848be6548decbe1e329491b4e"
},
"/home/emil/Desktop/Verbatim/scenarios/acid_eats_through_wall.json": {
"mtime": 1781984503.8460147,
"ast_hash": "2beb1ff35d7487a83fab52c75904d73e",
"semantic_hash": "2beb1ff35d7487a83fab52c75904d73e"
},
"/home/emil/Desktop/Verbatim/scenarios/entity_burns_in_lava.json": {
"mtime": 1781970726.5528774,
"ast_hash": "75f62c9cf20a5c144f6f389d7b1d5f5a",
"semantic_hash": "75f62c9cf20a5c144f6f389d7b1d5f5a"
},
"/home/emil/Desktop/Verbatim/scenarios/entity_survives_fall.json": {
"mtime": 1781983490.8671792,
"ast_hash": "4cc7a6f511896c0c5a1ab614d899fa2d",
"semantic_hash": "4cc7a6f511896c0c5a1ab614d899fa2d"
},
"/home/emil/Desktop/Verbatim/scenarios/fire_chain_wood.json": {
"mtime": 1781983468.9059443,
"ast_hash": "14b91384dff483d49dddc9d99b5041a4",
"semantic_hash": "14b91384dff483d49dddc9d99b5041a4"
},
"/home/emil/Desktop/Verbatim/scenarios/fire_spreads_through_grass.json": {
"mtime": 1781970725.561867,
"ast_hash": "a6f1b65d9a660266fd81ab4c0570fa15",
"semantic_hash": "a6f1b65d9a660266fd81ab4c0570fa15"
},
"/home/emil/Desktop/Verbatim/scenarios/lava_ignites_wood.json": {
"mtime": 1781970722.357833,
"ast_hash": "3c3975c955f54dc092000d2b8cbab980",
"semantic_hash": "3c3975c955f54dc092000d2b8cbab980"
},
"/home/emil/Desktop/Verbatim/scenarios/lava_plus_water_makes_steam.json": {
"mtime": 1781984425.8851807,
"ast_hash": "9deb9ec56c2d0f581874aa9d51be1438",
"semantic_hash": "9deb9ec56c2d0f581874aa9d51be1438"
},
"/home/emil/Desktop/Verbatim/scenarios/lava_pool_interaction.json": {
"mtime": 1781984476.6377237,
"ast_hash": "cab0da83d6903a07ae1054e710f9d108",
"semantic_hash": "cab0da83d6903a07ae1054e710f9d108"
},
"/home/emil/Desktop/Verbatim/scenarios/player_falls_and_lands.json": {
"mtime": 1781970725.561867,
"ast_hash": "c4264a79dea6301828ba05a892ea513e",
"semantic_hash": "c4264a79dea6301828ba05a892ea513e"
},
"/home/emil/Desktop/Verbatim/scenarios/sand_buries_wood.json": {
"mtime": 1781983484.682113,
"ast_hash": "1220b84d088ec7a35777416ffcb7ad16",
"semantic_hash": "1220b84d088ec7a35777416ffcb7ad16"
},
"/home/emil/Desktop/Verbatim/scenarios/sand_falls_down.json": {
"mtime": 1781970712.2597258,
"ast_hash": "30baf2c5ef87bc287a16e99eb6bad98f",
"semantic_hash": "30baf2c5ef87bc287a16e99eb6bad98f"
},
"/home/emil/Desktop/Verbatim/scenarios/steam_condenses.json": {
"mtime": 1781983498.8702648,
"ast_hash": "e59d6d0f5c9688bc0fcf88b63882d7f0",
"semantic_hash": "e59d6d0f5c9688bc0fcf88b63882d7f0"
},
"/home/emil/Desktop/Verbatim/scenarios/water_flows_sideways.json": {
"mtime": 1781970716.227768,
"ast_hash": "2aed38e5196f3331f69f7b2c8272fe9d",
"semantic_hash": "2aed38e5196f3331f69f7b2c8272fe9d"
},
"/home/emil/Desktop/Verbatim/src/ai/action.rs": {
"mtime": 1781982011.0213413,
"ast_hash": "c514af97d56e023db3e14776cebd742b",
"semantic_hash": "c514af97d56e023db3e14776cebd742b"
},
"/home/emil/Desktop/Verbatim/src/ai/mod.rs": {
"mtime": 1781970069.3378994,
"ast_hash": "37cfd173334195bb77ce77032e734475",
"semantic_hash": "37cfd173334195bb77ce77032e734475"
},
"/home/emil/Desktop/Verbatim/src/ai/protocol.rs": {
"mtime": 1781982011.0213413,
"ast_hash": "35c87a0bc8eea05d7973c0646723f26e",
"semantic_hash": "35c87a0bc8eea05d7973c0646723f26e"
},
"/home/emil/Desktop/Verbatim/src/ai/replay.rs": {
"mtime": 1781994698.517593,
"ast_hash": "bb610684494750685093b6745cbd3c95",
"semantic_hash": "bb610684494750685093b6745cbd3c95"
},
"/home/emil/Desktop/Verbatim/src/ai/scenario.rs": {
"mtime": 1781982011.0213413,
"ast_hash": "1ed4faf1ab6157642642e77372821df2",
"semantic_hash": "1ed4faf1ab6157642642e77372821df2"
},
"/home/emil/Desktop/Verbatim/src/ai/session.rs": {
"mtime": 1781994682.1954203,
"ast_hash": "299982f384ea4ee845e16462aa5c55ce",
"semantic_hash": "299982f384ea4ee845e16462aa5c55ce"
},
"/home/emil/Desktop/Verbatim/src/ai/state.rs": {
"mtime": 1781994883.4095483,
"ast_hash": "5b25a5fffc6be4182745780e04e40614",
"semantic_hash": "5b25a5fffc6be4182745780e04e40614"
},
"/home/emil/Desktop/Verbatim/src/entity/entity.rs": {
"mtime": 1781994535.1578653,
"ast_hash": "dc597c2796cd69c6703d50be701ce9f0",
"semantic_hash": "dc597c2796cd69c6703d50be701ce9f0"
},
"/home/emil/Desktop/Verbatim/src/entity/mod.rs": {
"mtime": 1781994733.5389633,
"ast_hash": "d720a8686f72b3f739a71acb1db16f18",
"semantic_hash": "d720a8686f72b3f739a71acb1db16f18"
},
"/home/emil/Desktop/Verbatim/src/entity/player.rs": {
"mtime": 1781994571.4262488,
"ast_hash": "ddfd3ca0846c649df864ae44ff1dc64c",
"semantic_hash": "ddfd3ca0846c649df864ae44ff1dc64c"
},
"/home/emil/Desktop/Verbatim/src/game.rs": {
"mtime": 1781994816.6048417,
"ast_hash": "7148e19d75808d22d6581639a8d5c311",
"semantic_hash": "7148e19d75808d22d6581639a8d5c311"
},
"/home/emil/Desktop/Verbatim/src/input.rs": {
"mtime": 1781994775.8534107,
"ast_hash": "70ec1ff16bf13b3649f27bea9a3891c8",
"semantic_hash": "70ec1ff16bf13b3649f27bea9a3891c8"
},
"/home/emil/Desktop/Verbatim/src/lib.rs": {
"mtime": 1781970270.862039,
"ast_hash": "abe33261862db85f4f4007a98efe1720",
"semantic_hash": "abe33261862db85f4f4007a98efe1720"
},
"/home/emil/Desktop/Verbatim/src/main.rs": {
"mtime": 1781994426.1417122,
"ast_hash": "f8186418f5cfccdc11af1976c10d994f",
"semantic_hash": "f8186418f5cfccdc11af1976c10d994f"
},
"/home/emil/Desktop/Verbatim/src/physics/collision.rs": {
"mtime": 1781969161.8692644,
"ast_hash": "bb0015e3335e47d8b25f3fa0a25a9afa",
"semantic_hash": "bb0015e3335e47d8b25f3fa0a25a9afa"
},
"/home/emil/Desktop/Verbatim/src/physics/mod.rs": {
"mtime": 1781994728.4279091,
"ast_hash": "b6cfc7b3d47ddf5dfe4b55385b1aeb0e",
"semantic_hash": "b6cfc7b3d47ddf5dfe4b55385b1aeb0e"
},
"/home/emil/Desktop/Verbatim/src/physics/verlet.rs": {
"mtime": 1781994612.3436816,
"ast_hash": "cdb55409206655f6826201b3a25d859f",
"semantic_hash": "cdb55409206655f6826201b3a25d859f"
},
"/home/emil/Desktop/Verbatim/src/render/graphics.rs": {
"mtime": 1781994837.8480663,
"ast_hash": "c0f8cc8af597d07af099e307622b4f2b",
"semantic_hash": "c0f8cc8af597d07af099e307622b4f2b"
},
"/home/emil/Desktop/Verbatim/src/render/mod.rs": {
"mtime": 1781992655.9960363,
"ast_hash": "1390b4f260548f51898abeaefa44c0d7",
"semantic_hash": "1390b4f260548f51898abeaefa44c0d7"
},
"/home/emil/Desktop/Verbatim/src/render/terminal.rs": {
"mtime": 1781994877.423485,
"ast_hash": "e9eb6b706e04619794e5567b7ae71425",
"semantic_hash": "e9eb6b706e04619794e5567b7ae71425"
},
"/home/emil/Desktop/Verbatim/src/render/vulkan.rs": {
"mtime": 1781994804.0007086,
"ast_hash": "d94730a5b91d78bb5271b7d18123fb5f",
"semantic_hash": "d94730a5b91d78bb5271b7d18123fb5f"
},
"/home/emil/Desktop/Verbatim/src/render/window_input.rs": {
"mtime": 1781994633.7179077,
"ast_hash": "e8d2a0128959ce134a3e440e7fdef8cf",
"semantic_hash": "e8d2a0128959ce134a3e440e7fdef8cf"
},
"/home/emil/Desktop/Verbatim/src/world/cell.rs": {
"mtime": 1781994439.0328486,
"ast_hash": "65f721a9878492ed06b059d605630c88",
"semantic_hash": "65f721a9878492ed06b059d605630c88"
},
"/home/emil/Desktop/Verbatim/src/world/cellular.rs": {
"mtime": 1781994620.518768,
"ast_hash": "4299c4f6dffd685c4cff62d6da00962f",
"semantic_hash": "4299c4f6dffd685c4cff62d6da00962f"
},
"/home/emil/Desktop/Verbatim/src/world/grid.rs": {
"mtime": 1781994901.0097344,
"ast_hash": "7e28468cb7d0e44dd95c663c45b523b3",
"semantic_hash": "7e28468cb7d0e44dd95c663c45b523b3"
},
"/home/emil/Desktop/Verbatim/src/world/material.rs": {
"mtime": 1781994717.9467983,
"ast_hash": "7f70daf6658c7240dc576ca875dbdad0",
"semantic_hash": "7f70daf6658c7240dc576ca875dbdad0"
},
"/home/emil/Desktop/Verbatim/src/world/mod.rs": {
"mtime": 1781994722.7638493,
"ast_hash": "72a8eea3bfe44a1ff11a4c69709d414e",
"semantic_hash": "72a8eea3bfe44a1ff11a4c69709d414e"
},
"/home/emil/Desktop/Verbatim/tests/collision_robust.rs": {
"mtime": 1781984005.8506877,
"ast_hash": "aa42931beb7fc8c329f8ac1041a3c6dd",
"semantic_hash": "aa42931beb7fc8c329f8ac1041a3c6dd"
},
"/home/emil/Desktop/Verbatim/tests/determinism.rs": {
"mtime": 1781983582.462159,
"ast_hash": "22197294f9d488ea47da4c528d2748f5",
"semantic_hash": "22197294f9d488ea47da4c528d2748f5"
},
"/home/emil/Desktop/Verbatim/tests/edge_cases.rs": {
"mtime": 1781994760.8232517,
"ast_hash": "161c3c3c243335166e3bbb568b2c74a4",
"semantic_hash": "161c3c3c243335166e3bbb568b2c74a4"
},
"/home/emil/Desktop/Verbatim/tests/entity_damage.rs": {
"mtime": 1781980721.7435043,
"ast_hash": "2deee58dcc1b6b44e8d25a48c0fc5897",
"semantic_hash": "2deee58dcc1b6b44e8d25a48c0fc5897"
},
"/home/emil/Desktop/Verbatim/tests/entity_movement.rs": {
"mtime": 1781981346.368212,
"ast_hash": "049056e445056bf6c4b0e2a70c187fb1",
"semantic_hash": "049056e445056bf6c4b0e2a70c187fb1"
},
"/home/emil/Desktop/Verbatim/tests/integration.rs": {
"mtime": 1781983295.271087,
"ast_hash": "8db0aae4fbd95fb1effd18bb8560f953",
"semantic_hash": "8db0aae4fbd95fb1effd18bb8560f953"
},
"/home/emil/Desktop/Verbatim/tests/physics_acid.rs": {
"mtime": 1781970221.685517,
"ast_hash": "e6034490a9607deeadce03505cadf731",
"semantic_hash": "e6034490a9607deeadce03505cadf731"
},
"/home/emil/Desktop/Verbatim/tests/physics_interactions.rs": {
"mtime": 1781983882.030363,
"ast_hash": "38b73ebc6c62cfefb20506db6dd1072d",
"semantic_hash": "38b73ebc6c62cfefb20506db6dd1072d"
},
"/home/emil/Desktop/Verbatim/tests/physics_lava.rs": {
"mtime": 1781980754.4328554,
"ast_hash": "c31c44a9f63bf8b6f1df51737f1a2de9",
"semantic_hash": "c31c44a9f63bf8b6f1df51737f1a2de9"
},
"/home/emil/Desktop/Verbatim/tests/physics_materials.rs": {
"mtime": 1781983693.2113435,
"ast_hash": "ef6d9d77564f4c02745639ec5807d7db",
"semantic_hash": "ef6d9d77564f4c02745639ec5807d7db"
},
"/home/emil/Desktop/Verbatim/tests/physics_sand.rs": {
"mtime": 1781983289.756028,
"ast_hash": "4b799fe61ebaa00d14c570d9284a37ec",
"semantic_hash": "4b799fe61ebaa00d14c570d9284a37ec"
},
"/home/emil/Desktop/Verbatim/tests/physics_water.rs": {
"mtime": 1781970629.040842,
"ast_hash": "a7141937d2e90d6cc16a6afef026bb1e",
"semantic_hash": "a7141937d2e90d6cc16a6afef026bb1e"
},
"/home/emil/Desktop/Verbatim/tests/player_controls.rs": {
"mtime": 1781984330.918165,
"ast_hash": "da75dda146361ea0e23ebd11f554fdba",
"semantic_hash": "da75dda146361ea0e23ebd11f554fdba"
},
"/home/emil/Desktop/Verbatim/tests/ragdoll_death.rs": {
"mtime": 1781994739.1590228,
"ast_hash": "c4dfb537ecc663fbb2444de321e05385",
"semantic_hash": "c4dfb537ecc663fbb2444de321e05385"
},
"/home/emil/Desktop/Verbatim/AGENTS.md": {
"mtime": 1781995334.9053233,
"ast_hash": "c301fc82f1fa9473c0354454b9b5ad55",
"semantic_hash": "c301fc82f1fa9473c0354454b9b5ad55"
},
"/home/emil/Desktop/Verbatim/PLAN.md": {
"mtime": 1781995156.2924342,
"ast_hash": "cb55659d25cbbb523394cc0c383bff5a",
"semantic_hash": "cb55659d25cbbb523394cc0c383bff5a"
},
"/home/emil/Desktop/Verbatim/headless_dump.txt": {
"mtime": 1781989911.9880815,
"ast_hash": "1ab2647a155a1eed5d05b7292a29b63e",
"semantic_hash": "1ab2647a155a1eed5d05b7292a29b63e"
}
}
+129 -40
View File
@@ -1,18 +1,21 @@
use clap::Parser; use clap::Parser;
use std::io::Write;
use std::sync::Arc;
use std::time::{Duration, Instant};
use verbatim::ai;
use verbatim::game::Game; use verbatim::game::Game;
use verbatim::render::terminal::TerminalRenderer; use verbatim::render::terminal::TerminalRenderer;
use verbatim::render::window_input::WindowInput; use verbatim::render::window_input::WindowInput;
use verbatim::world::cell::MaterialId; use verbatim::world::cell::MaterialId;
use verbatim::ai;
use std::io::Write;
use std::sync::Arc;
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::{EventLoop, ControlFlow}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::Window; use winit::window::Window;
use std::time::{Duration, Instant};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "verbatim", about = "ASCII physics RPG - Noita meets Caves of Qud")] #[command(
name = "verbatim",
about = "ASCII physics RPG - Noita meets Caves of Qud"
)]
struct Cli { struct Cli {
#[arg(long, default_value = "ascii")] #[arg(long, default_value = "ascii")]
mode: String, mode: String,
@@ -31,8 +34,16 @@ struct Cli {
} }
trait GpuRenderer { trait GpuRenderer {
fn new(window: Arc<Window>) -> Result<Self, String> where Self: Sized; fn new(window: Arc<Window>) -> Result<Self, String>
fn render(&mut self, grid: &verbatim::world::grid::Grid, entities: &verbatim::entity::EntityManager, cam_x: i32, cam_y: i32); where
Self: Sized;
fn render(
&mut self,
grid: &verbatim::world::grid::Grid,
entities: &verbatim::entity::EntityManager,
cam_x: i32,
cam_y: i32,
);
fn grid_w(&self) -> usize; fn grid_w(&self) -> usize;
fn grid_h(&self) -> usize; fn grid_h(&self) -> usize;
} }
@@ -41,22 +52,42 @@ impl GpuRenderer for verbatim::render::vulkan::VulkanRenderer {
fn new(window: Arc<Window>) -> Result<Self, String> { fn new(window: Arc<Window>) -> Result<Self, String> {
verbatim::render::vulkan::VulkanRenderer::new(window) verbatim::render::vulkan::VulkanRenderer::new(window)
} }
fn render(&mut self, grid: &verbatim::world::grid::Grid, entities: &verbatim::entity::EntityManager, cam_x: i32, cam_y: i32) { fn render(
&mut self,
grid: &verbatim::world::grid::Grid,
entities: &verbatim::entity::EntityManager,
cam_x: i32,
cam_y: i32,
) {
verbatim::render::vulkan::VulkanRenderer::render(self, grid, entities, cam_x, cam_y) verbatim::render::vulkan::VulkanRenderer::render(self, grid, entities, cam_x, cam_y)
} }
fn grid_w(&self) -> usize { verbatim::render::vulkan::VulkanRenderer::grid_w(self) } fn grid_w(&self) -> usize {
fn grid_h(&self) -> usize { verbatim::render::vulkan::VulkanRenderer::grid_h(self) } verbatim::render::vulkan::VulkanRenderer::grid_w(self)
}
fn grid_h(&self) -> usize {
verbatim::render::vulkan::VulkanRenderer::grid_h(self)
}
} }
impl GpuRenderer for verbatim::render::graphics::GraphicsRenderer { impl GpuRenderer for verbatim::render::graphics::GraphicsRenderer {
fn new(window: Arc<Window>) -> Result<Self, String> { fn new(window: Arc<Window>) -> Result<Self, String> {
verbatim::render::graphics::GraphicsRenderer::new(window) verbatim::render::graphics::GraphicsRenderer::new(window)
} }
fn render(&mut self, grid: &verbatim::world::grid::Grid, entities: &verbatim::entity::EntityManager, cam_x: i32, cam_y: i32) { fn render(
&mut self,
grid: &verbatim::world::grid::Grid,
entities: &verbatim::entity::EntityManager,
cam_x: i32,
cam_y: i32,
) {
verbatim::render::graphics::GraphicsRenderer::render(self, grid, entities, cam_x, cam_y) verbatim::render::graphics::GraphicsRenderer::render(self, grid, entities, cam_x, cam_y)
} }
fn grid_w(&self) -> usize { verbatim::render::graphics::GraphicsRenderer::grid_w(self) } fn grid_w(&self) -> usize {
fn grid_h(&self) -> usize { verbatim::render::graphics::GraphicsRenderer::grid_h(self) } verbatim::render::graphics::GraphicsRenderer::grid_w(self)
}
fn grid_h(&self) -> usize {
verbatim::render::graphics::GraphicsRenderer::grid_h(self)
}
} }
fn main() { fn main() {
@@ -103,7 +134,10 @@ fn main() {
} }
} }
_ => { _ => {
eprintln!("Unknown mode: {}. Use terminal, ascii, graphics, pipe, test, replay, or headless.", cli.mode); eprintln!(
"Unknown mode: {}. Use terminal, ascii, graphics, pipe, test, replay, or headless.",
cli.mode
);
std::process::exit(1); std::process::exit(1);
} }
} }
@@ -111,11 +145,13 @@ fn main() {
fn run_gpu_mode<R: GpuRenderer>(title: &str) { fn run_gpu_mode<R: GpuRenderer>(title: &str) {
let event_loop = EventLoop::new().expect("Failed to create event loop"); let event_loop = EventLoop::new().expect("Failed to create event loop");
let window = event_loop.create_window( let window = event_loop
.create_window(
Window::default_attributes() Window::default_attributes()
.with_title(title) .with_title(title)
.with_inner_size(winit::dpi::LogicalSize::new(160 * 16, 50 * 16)) .with_inner_size(winit::dpi::LogicalSize::new(160 * 10, 50 * 10)),
).expect("Failed to create window"); )
.expect("Failed to create window");
let window = Arc::new(window); let window = Arc::new(window);
let mut renderer = match R::new(Arc::clone(&window)) { let mut renderer = match R::new(Arc::clone(&window)) {
@@ -140,22 +176,23 @@ fn run_gpu_mode<R: GpuRenderer>(title: &str) {
let mut accumulator = Duration::ZERO; let mut accumulator = Duration::ZERO;
let mut running = true; let mut running = true;
event_loop.run(|event, ctrl| { event_loop
.run(|event, ctrl| {
ctrl.set_control_flow(ControlFlow::Poll); ctrl.set_control_flow(ControlFlow::Poll);
match event { match event {
Event::WindowEvent { event, .. } => { Event::WindowEvent { event, .. } => match event {
match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
running = false; running = false;
ctrl.exit(); ctrl.exit();
} }
WindowEvent::KeyboardInput { event: key_event, .. } => { WindowEvent::KeyboardInput {
event: key_event, ..
} => {
input.on_key_event(key_event.physical_key, key_event.state); input.on_key_event(key_event.physical_key, key_event.state);
} }
_ => {} _ => {}
} },
}
Event::AboutToWait => { Event::AboutToWait => {
if !running { if !running {
ctrl.exit(); ctrl.exit();
@@ -198,10 +235,18 @@ fn run_gpu_mode<R: GpuRenderer>(title: &str) {
game.player.stop_horizontal(&mut game.entities); game.player.stop_horizontal(&mut game.entities);
} }
if input.cam_left { game.cam_x -= 3; } if input.cam_left {
if input.cam_right { game.cam_x += 3; } game.cam_x -= 3;
if input.cam_up { game.cam_y -= 3; } }
if input.cam_down { game.cam_y += 3; } if input.cam_right {
game.cam_x += 3;
}
if input.cam_up {
game.cam_y -= 3;
}
if input.cam_down {
game.cam_y += 3;
}
if let Some(brush_id) = input.paint { if let Some(brush_id) = input.paint {
let mat = match brush_id { let mat = match brush_id {
@@ -225,7 +270,11 @@ fn run_gpu_mode<R: GpuRenderer>(title: &str) {
for dx in -r..=r { for dx in -r..=r {
if dx * dx + dy * dy <= r * r + 1 { if dx * dx + dy * dy <= r * r + 1 {
if mat == MaterialId::Empty { if mat == MaterialId::Empty {
game.grid.set(cx + dx, cy + dy, verbatim::world::cell::Cell::empty()); game.grid.set(
cx + dx,
cy + dy,
verbatim::world::cell::Cell::empty(),
);
} else { } else {
game.grid.set_material(cx + dx, cy + dy, mat); game.grid.set_material(cx + dx, cy + dy, mat);
} }
@@ -242,7 +291,8 @@ fn run_gpu_mode<R: GpuRenderer>(title: &str) {
} }
_ => {} _ => {}
} }
}).expect("event loop error"); })
.expect("event loop error");
} }
fn run_test_mode(cli: &Cli) { fn run_test_mode(cli: &Cli) {
@@ -292,8 +342,10 @@ fn run_replay_mode(cli: &Cli) {
println!("=== Replay: {} events ===", player.recording().events.len()); println!("=== Replay: {} events ===", player.recording().events.len());
println!("Final tick: {}", state.tick); println!("Final tick: {}", state.tick);
if let Some(ref p) = state.player { if let Some(ref p) = state.player {
println!("Player: {} hp={:.1}/{:.1} pos=({:.1},{:.1}) alive={}", println!(
p.kind, p.health, p.max_health, p.pos[0], p.pos[1], p.alive); "Player: {} hp={:.1}/{:.1} pos=({:.1},{:.1}) alive={}",
p.kind, p.health, p.max_health, p.pos[0], p.pos[1], p.alive
);
} }
println!("Entities: {}", state.entities.len()); println!("Entities: {}", state.entities.len());
println!("\nFinal view:\n{}", state.view); println!("\nFinal view:\n{}", state.view);
@@ -314,8 +366,14 @@ fn run_headless(ticks: u32) {
let cam_y = py as i32 - 12; let cam_y = py as i32 - 12;
let mut log = String::new(); let mut log = String::new();
log.push_str(&format!("=== Verbatim Headless Run: {} ticks ===\n\n", ticks)); log.push_str(&format!(
log.push_str(&format!("World: {}x{}\n", game.grid.width, game.grid.height)); "=== Verbatim Headless Run: {} ticks ===\n\n",
ticks
));
log.push_str(&format!(
"World: {}x{}\n",
game.grid.width, game.grid.height
));
log.push_str(&format!("Player start: ({:.1}, {:.1})\n", px, py)); log.push_str(&format!("Player start: ({:.1}, {:.1})\n", px, py));
log.push_str(&format!("Camera: ({}, {})\n\n", cam_x, cam_y)); log.push_str(&format!("Camera: ({}, {})\n\n", cam_x, cam_y));
@@ -339,9 +397,19 @@ fn run_headless(ticks: u32) {
log.push_str(&format!("Player: {:?}\n", player_info(&game))); log.push_str(&format!("Player: {:?}\n", player_info(&game)));
log.push_str(&format!("Entities: {}\n", game.entities.all().len())); log.push_str(&format!("Entities: {}\n", game.entities.all().len()));
let alive: Vec<_> = game.entities.all().iter() let alive: Vec<_> = game
.entities
.all()
.iter()
.filter(|e| e.alive) .filter(|e| e.alive)
.map(|e| format!("{}(hp={:.0}, pos={:?})", ai::entity_kind_name(e.kind), e.health, e.center())) .map(|e| {
format!(
"{}(hp={:.0}, pos={:?})",
ai::entity_kind_name(e.kind),
e.health,
e.center()
)
})
.collect(); .collect();
log.push_str(&format!("Alive entities: {}\n\n", alive.join(", "))); log.push_str(&format!("Alive entities: {}\n\n", alive.join(", ")));
} }
@@ -349,16 +417,27 @@ fn run_headless(ticks: u32) {
let mut f = std::fs::File::create("headless_dump.txt").expect("Cannot create dump file"); let mut f = std::fs::File::create("headless_dump.txt").expect("Cannot create dump file");
f.write_all(log.as_bytes()).expect("Cannot write dump"); f.write_all(log.as_bytes()).expect("Cannot write dump");
eprintln!("Headless run complete: {} ticks, dump written to headless_dump.txt", ticks); eprintln!(
"Headless run complete: {} ticks, dump written to headless_dump.txt",
ticks
);
} }
fn dump_view(grid: &verbatim::world::grid::Grid, entities: &verbatim::entity::EntityManager, cam_x: i32, cam_y: i32, vw: usize, vh: usize) -> String { fn dump_view(
grid: &verbatim::world::grid::Grid,
entities: &verbatim::entity::EntityManager,
cam_x: i32,
cam_y: i32,
vw: usize,
vh: usize,
) -> String {
ai::render_view(grid, entities, cam_x, cam_y, vw, vh) ai::render_view(grid, entities, cam_x, cam_y, vw, vh)
.lines() .lines()
.enumerate() .enumerate()
.map(|(i, line)| format!("{:2}{}", (cam_y + i as i32) % 100, line)) .map(|(i, line)| format!("{:2}{}", (cam_y + i as i32) % 100, line))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n") + "\n" .join("\n")
+ "\n"
} }
fn player_info(game: &Game) -> String { fn player_info(game: &Game) -> String {
@@ -367,7 +446,17 @@ fn player_info(game: &Game) -> String {
let body_count = e.bodies.iter().filter(|b| b.alive).count(); let body_count = e.bodies.iter().filter(|b| b.alive).count();
let on_fire = e.on_fire; let on_fire = e.on_fire;
let kind = ai::entity_kind_name(e.kind); let kind = ai::entity_kind_name(e.kind);
format!("{} hp={:.1}/{:.1} pos=({:.1},{:.1}) bodies={}/{} on_fire={}", kind, e.health, e.max_health, cx, cy, body_count, e.bodies.len(), on_fire) format!(
"{} hp={:.1}/{:.1} pos=({:.1},{:.1}) bodies={}/{} on_fire={}",
kind,
e.health,
e.max_health,
cx,
cy,
body_count,
e.bodies.len(),
on_fire
)
} else { } else {
"None".to_string() "None".to_string()
} }
+507 -155
View File
@@ -2,12 +2,12 @@ use ash::vk;
use std::ffi::CString; use std::ffi::CString;
use std::sync::Arc; use std::sync::Arc;
use crate::entity::{EntityManager, EntityKind}; use crate::entity::{EntityKind, EntityManager};
use crate::world::cell::MaterialId; use crate::world::cell::MaterialId;
use crate::world::grid::Grid; use crate::world::grid::Grid;
const CHAR_W: u32 = 16; const CHAR_W: u32 = 10;
const CHAR_H: u32 = 16; const CHAR_H: u32 = 10;
const MAX_FRAMES: usize = 2; const MAX_FRAMES: usize = 2;
#[repr(C)] #[repr(C)]
@@ -75,7 +75,9 @@ impl GraphicsRenderer {
let entry = unsafe { ash::Entry::load().map_err(|e| format!("Vulkan load: {e}"))? }; let entry = unsafe { ash::Entry::load().map_err(|e| format!("Vulkan load: {e}"))? };
use raw_window_handle::HasDisplayHandle; use raw_window_handle::HasDisplayHandle;
let dh = window.display_handle().map_err(|e| format!("display_handle: {e}"))?; let dh = window
.display_handle()
.map_err(|e| format!("display_handle: {e}"))?;
let required_exts = ash_window::enumerate_required_extensions(dh.as_raw()) let required_exts = ash_window::enumerate_required_extensions(dh.as_raw())
.map_err(|e| format!("enumerate_required_extensions: {e:?}"))?; .map_err(|e| format!("enumerate_required_extensions: {e:?}"))?;
@@ -84,15 +86,20 @@ impl GraphicsRenderer {
.application_name(&app_name) .application_name(&app_name)
.api_version(vk::API_VERSION_1_2); .api_version(vk::API_VERSION_1_2);
let mut ext_ptrs: Vec<*const i8> = required_exts.iter().map(|&p| p as *const i8).collect(); let mut ext_ptrs: Vec<*const i8> = required_exts.iter().map(|&p| p as *const i8).collect();
let avail_exts = unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default(); let avail_exts =
unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default();
let has_debug = avail_exts.iter().any(|e| { let has_debug = avail_exts.iter().any(|e| {
let name = unsafe { std::ffi::CStr::from_ptr(e.extension_name.as_ptr() as *const i8) }; let name = unsafe { std::ffi::CStr::from_ptr(e.extension_name.as_ptr() as *const i8) };
name.to_str().unwrap_or("") == "VK_EXT_debug_utils" name.to_str().unwrap_or("") == "VK_EXT_debug_utils"
}); });
if has_debug { ext_ptrs.push(b"VK_EXT_debug_utils\0".as_ptr() as *const i8); } if has_debug {
ext_ptrs.push(b"VK_EXT_debug_utils\0".as_ptr() as *const i8);
}
let ci = vk::InstanceCreateInfo::default() let ci = vk::InstanceCreateInfo::default()
.application_info(&app_info).enabled_extension_names(&ext_ptrs); .application_info(&app_info)
let instance = unsafe { entry.create_instance(&ci, None) }.map_err(|e| format!("instance: {e:?}"))?; .enabled_extension_names(&ext_ptrs);
let instance =
unsafe { entry.create_instance(&ci, None) }.map_err(|e| format!("instance: {e:?}"))?;
let surface = { let surface = {
use raw_window_handle::HasWindowHandle; use raw_window_handle::HasWindowHandle;
@@ -103,70 +110,118 @@ impl GraphicsRenderer {
}; };
let sl = ash::khr::surface::Instance::new(&entry, &instance); let sl = ash::khr::surface::Instance::new(&entry, &instance);
let devices = unsafe { instance.enumerate_physical_devices() }.map_err(|e| format!("enum: {e:?}"))?; let devices =
unsafe { instance.enumerate_physical_devices() }.map_err(|e| format!("enum: {e:?}"))?;
let mut physical_device = vk::PhysicalDevice::null(); let mut physical_device = vk::PhysicalDevice::null();
let mut qf = 0u32; let mut qf = 0u32;
for &pd in &devices { for &pd in &devices {
let props = unsafe { instance.get_physical_device_properties(pd) }; let props = unsafe { instance.get_physical_device_properties(pd) };
if props.device_type == vk::PhysicalDeviceType::CPU { continue; } if props.device_type == vk::PhysicalDeviceType::CPU {
continue;
}
let qfs = unsafe { instance.get_physical_device_queue_family_properties(pd) }; let qfs = unsafe { instance.get_physical_device_queue_family_properties(pd) };
for (i, q) in qfs.iter().enumerate() { for (i, q) in qfs.iter().enumerate() {
if q.queue_flags.contains(vk::QueueFlags::GRAPHICS) { if q.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
let ok = unsafe { sl.get_physical_device_surface_support(pd, i as u32, surface) }.unwrap_or(false); let ok =
if ok { physical_device = pd; qf = i as u32; break; } unsafe { sl.get_physical_device_surface_support(pd, i as u32, surface) }
.unwrap_or(false);
if ok {
physical_device = pd;
qf = i as u32;
break;
} }
} }
if physical_device != vk::PhysicalDevice::null() { break; }
} }
if physical_device == vk::PhysicalDevice::null() { return Err("No GPU".to_string()); } if physical_device != vk::PhysicalDevice::null() {
break;
}
}
if physical_device == vk::PhysicalDevice::null() {
return Err("No GPU".to_string());
}
let qp = [1.0f32]; let qp = [1.0f32];
let qi = vk::DeviceQueueCreateInfo::default().queue_family_index(qf).queue_priorities(&qp); let qi = vk::DeviceQueueCreateInfo::default()
.queue_family_index(qf)
.queue_priorities(&qp);
let dev_ext_names: Vec<CString> = vec![CString::new("VK_KHR_swapchain").unwrap()]; let dev_ext_names: Vec<CString> = vec![CString::new("VK_KHR_swapchain").unwrap()];
let dev_ext_ptrs: Vec<*const i8> = dev_ext_names.iter().map(|n| n.as_ptr()).collect(); let dev_ext_ptrs: Vec<*const i8> = dev_ext_names.iter().map(|n| n.as_ptr()).collect();
let dci = vk::DeviceCreateInfo::default() let dci = vk::DeviceCreateInfo::default()
.queue_create_infos(std::slice::from_ref(&qi)) .queue_create_infos(std::slice::from_ref(&qi))
.enabled_extension_names(&dev_ext_ptrs); .enabled_extension_names(&dev_ext_ptrs);
let device = unsafe { instance.create_device(physical_device, &dci, None) }.map_err(|e| format!("device: {e:?}"))?; let device = unsafe { instance.create_device(physical_device, &dci, None) }
.map_err(|e| format!("device: {e:?}"))?;
let graphics_queue = unsafe { device.get_device_queue(qf, 0) }; let graphics_queue = unsafe { device.get_device_queue(qf, 0) };
let swapchain_loader = ash::khr::swapchain::Device::new(&instance, &device); let swapchain_loader = ash::khr::swapchain::Device::new(&instance, &device);
let caps = unsafe { sl.get_physical_device_surface_capabilities(physical_device, surface) }.map_err(|e| format!("caps: {e:?}"))?; let caps = unsafe { sl.get_physical_device_surface_capabilities(physical_device, surface) }
let format = vk::SurfaceFormatKHR { format: vk::Format::B8G8R8A8_UNORM, color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR }; .map_err(|e| format!("caps: {e:?}"))?;
let extent = if caps.current_extent.width != u32::MAX { caps.current_extent } let format = vk::SurfaceFormatKHR {
else { vk::Extent2D { width: grid_w as u32 * CHAR_W, height: grid_h as u32 * CHAR_H } }; format: vk::Format::B8G8R8A8_UNORM,
color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
};
let extent = if caps.current_extent.width != u32::MAX {
caps.current_extent
} else {
vk::Extent2D {
width: grid_w as u32 * CHAR_W,
height: grid_h as u32 * CHAR_H,
}
};
let ic = caps.min_image_count.max(2); let ic = caps.min_image_count.max(2);
let qf_slice = [qf]; let qf_slice = [qf];
let sci = vk::SwapchainCreateInfoKHR::default() let sci = vk::SwapchainCreateInfoKHR::default()
.surface(surface).min_image_count(ic) .surface(surface)
.image_format(format.format).image_color_space(format.color_space) .min_image_count(ic)
.image_extent(extent).image_array_layers(1) .image_format(format.format)
.image_color_space(format.color_space)
.image_extent(extent)
.image_array_layers(1)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE) .image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&qf_slice) .queue_family_indices(&qf_slice)
.pre_transform(caps.current_transform) .pre_transform(caps.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(vk::PresentModeKHR::FIFO).clipped(true); .present_mode(vk::PresentModeKHR::FIFO)
let swapchain = unsafe { swapchain_loader.create_swapchain(&sci, None) }.map_err(|e| format!("swapchain: {e:?}"))?; .clipped(true);
let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain) }.map_err(|e| format!("images: {e:?}"))?; let swapchain = unsafe { swapchain_loader.create_swapchain(&sci, None) }
let swapchain_image_views: Vec<_> = swapchain_images.iter() .map_err(|e| format!("swapchain: {e:?}"))?;
let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain) }
.map_err(|e| format!("images: {e:?}"))?;
let swapchain_image_views: Vec<_> = swapchain_images
.iter()
.map(|&img| { .map(|&img| {
let vi = vk::ImageViewCreateInfo::default() let vi = vk::ImageViewCreateInfo::default()
.image(img).view_type(vk::ImageViewType::TYPE_2D).format(format.format) .image(img)
.subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 }); .view_type(vk::ImageViewType::TYPE_2D)
.format(format.format)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
unsafe { device.create_image_view(&vi, None).expect("image_view") } unsafe { device.create_image_view(&vi, None).expect("image_view") }
}).collect(); })
.collect();
let att = vk::AttachmentDescription::default() let att = vk::AttachmentDescription::default()
.format(format.format).samples(vk::SampleCountFlags::TYPE_1) .format(format.format)
.load_op(vk::AttachmentLoadOp::CLEAR).store_op(vk::AttachmentStoreOp::STORE) .samples(vk::SampleCountFlags::TYPE_1)
.initial_layout(vk::ImageLayout::UNDEFINED).final_layout(vk::ImageLayout::PRESENT_SRC_KHR); .load_op(vk::AttachmentLoadOp::CLEAR)
let att_ref = vk::AttachmentReference::default().attachment(0).layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); .store_op(vk::AttachmentStoreOp::STORE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
let att_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default() let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&att_ref)); .color_attachments(std::slice::from_ref(&att_ref));
let dep = vk::SubpassDependency::default() let dep = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL).dst_subpass(0) .src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE); .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
@@ -174,67 +229,159 @@ impl GraphicsRenderer {
.attachments(std::slice::from_ref(&att)) .attachments(std::slice::from_ref(&att))
.subpasses(std::slice::from_ref(&subpass)) .subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dep)); .dependencies(std::slice::from_ref(&dep));
let render_pass = unsafe { device.create_render_pass(&rpci, None) }.map_err(|e| format!("render_pass: {e:?}"))?; let render_pass = unsafe { device.create_render_pass(&rpci, None) }
.map_err(|e| format!("render_pass: {e:?}"))?;
// Pipeline — no atlas, no descriptor set, just colored quads // Pipeline — no atlas, no descriptor set, just colored quads
let vert_spv = include_bytes!("../../assets/shaders/graphics_vert.spv"); let vert_spv = include_bytes!("../../assets/shaders/graphics_vert.spv");
let frag_spv = include_bytes!("../../assets/shaders/graphics_frag.spv"); let frag_spv = include_bytes!("../../assets/shaders/graphics_frag.spv");
let vert_code: Vec<u32> = vert_spv.chunks_exact(4).map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect(); let vert_code: Vec<u32> = vert_spv
let frag_code: Vec<u32> = frag_spv.chunks_exact(4).map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]])).collect(); .chunks_exact(4)
let vm = unsafe { device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&vert_code), None) }.map_err(|e| format!("vert: {e:?}"))?; .map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]]))
let fm = unsafe { device.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&frag_code), None) }.map_err(|e| format!("frag: {e:?}"))?; .collect();
let frag_code: Vec<u32> = frag_spv
.chunks_exact(4)
.map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]]))
.collect();
let vm = unsafe {
device.create_shader_module(
&vk::ShaderModuleCreateInfo::default().code(&vert_code),
None,
)
}
.map_err(|e| format!("vert: {e:?}"))?;
let fm = unsafe {
device.create_shader_module(
&vk::ShaderModuleCreateInfo::default().code(&frag_code),
None,
)
}
.map_err(|e| format!("frag: {e:?}"))?;
let main = CString::new("main").unwrap(); let main = CString::new("main").unwrap();
let vs = vk::PipelineShaderStageCreateInfo::default().stage(vk::ShaderStageFlags::VERTEX).module(vm).name(&main); let vs = vk::PipelineShaderStageCreateInfo::default()
let fs = vk::PipelineShaderStageCreateInfo::default().stage(vk::ShaderStageFlags::FRAGMENT).module(fm).name(&main); .stage(vk::ShaderStageFlags::VERTEX)
.module(vm)
.name(&main);
let fs = vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(fm)
.name(&main);
let bindings = [ let bindings = [
vk::VertexInputBindingDescription { binding: 0, stride: 8, input_rate: vk::VertexInputRate::VERTEX }, vk::VertexInputBindingDescription {
vk::VertexInputBindingDescription { binding: 1, stride: std::mem::size_of::<ColorInstance>() as u32, input_rate: vk::VertexInputRate::INSTANCE }, binding: 0,
stride: 8,
input_rate: vk::VertexInputRate::VERTEX,
},
vk::VertexInputBindingDescription {
binding: 1,
stride: std::mem::size_of::<ColorInstance>() as u32,
input_rate: vk::VertexInputRate::INSTANCE,
},
]; ];
let attrs = [ let attrs = [
vk::VertexInputAttributeDescription { location: 0, binding: 0, format: vk::Format::R32G32_SFLOAT, offset: 0 }, vk::VertexInputAttributeDescription {
vk::VertexInputAttributeDescription { location: 1, binding: 1, format: vk::Format::R32G32_SFLOAT, offset: 0 }, location: 0,
vk::VertexInputAttributeDescription { location: 2, binding: 1, format: vk::Format::R8G8B8A8_UNORM, offset: 8 }, binding: 0,
format: vk::Format::R32G32_SFLOAT,
offset: 0,
},
vk::VertexInputAttributeDescription {
location: 1,
binding: 1,
format: vk::Format::R32G32_SFLOAT,
offset: 0,
},
vk::VertexInputAttributeDescription {
location: 2,
binding: 1,
format: vk::Format::R8G8B8A8_UNORM,
offset: 8,
},
]; ];
let vi = vk::PipelineVertexInputStateCreateInfo::default() let vi = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings).vertex_attribute_descriptions(&attrs); .vertex_binding_descriptions(&bindings)
let ia = vk::PipelineInputAssemblyStateCreateInfo::default().topology(vk::PrimitiveTopology::TRIANGLE_LIST); .vertex_attribute_descriptions(&attrs);
let ia = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic_state = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); let dynamic_state =
let vs_state = vk::PipelineViewportStateCreateInfo::default().viewport_count(1).scissor_count(1); vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let rs = vk::PipelineRasterizationStateCreateInfo::default().line_width(1.0).cull_mode(vk::CullModeFlags::NONE); let vs_state = vk::PipelineViewportStateCreateInfo::default()
let ms = vk::PipelineMultisampleStateCreateInfo::default().rasterization_samples(vk::SampleCountFlags::TYPE_1); .viewport_count(1)
let cba = vk::PipelineColorBlendAttachmentState::default().color_write_mask(vk::ColorComponentFlags::RGBA); .scissor_count(1);
let cb = vk::PipelineColorBlendStateCreateInfo::default().attachments(std::slice::from_ref(&cba)); let rs = vk::PipelineRasterizationStateCreateInfo::default()
let pcr = vk::PushConstantRange { stage_flags: vk::ShaderStageFlags::VERTEX, offset: 0, size: std::mem::size_of::<PushConstants>() as u32 }; .line_width(1.0)
let pli = vk::PipelineLayoutCreateInfo::default().push_constant_ranges(std::slice::from_ref(&pcr)); .cull_mode(vk::CullModeFlags::NONE);
let pipeline_layout = unsafe { device.create_pipeline_layout(&pli, None) }.map_err(|e| format!("pipeline_layout: {e:?}"))?; let ms = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let cba = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA);
let cb = vk::PipelineColorBlendStateCreateInfo::default()
.attachments(std::slice::from_ref(&cba));
let pcr = vk::PushConstantRange {
stage_flags: vk::ShaderStageFlags::VERTEX,
offset: 0,
size: std::mem::size_of::<PushConstants>() as u32,
};
let pli = vk::PipelineLayoutCreateInfo::default()
.push_constant_ranges(std::slice::from_ref(&pcr));
let pipeline_layout = unsafe { device.create_pipeline_layout(&pli, None) }
.map_err(|e| format!("pipeline_layout: {e:?}"))?;
let stages = [vs, fs]; let stages = [vs, fs];
let gpci = vk::GraphicsPipelineCreateInfo::default() let gpci = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages).vertex_input_state(&vi).input_assembly_state(&ia) .stages(&stages)
.viewport_state(&vs_state).rasterization_state(&rs).multisample_state(&ms) .vertex_input_state(&vi)
.color_blend_state(&cb).dynamic_state(&dynamic_state) .input_assembly_state(&ia)
.layout(pipeline_layout).render_pass(render_pass).subpass(0); .viewport_state(&vs_state)
let pipes = unsafe { device.create_graphics_pipelines(vk::PipelineCache::null(), std::slice::from_ref(&gpci), None) } .rasterization_state(&rs)
.multisample_state(&ms)
.color_blend_state(&cb)
.dynamic_state(&dynamic_state)
.layout(pipeline_layout)
.render_pass(render_pass)
.subpass(0);
let pipes = unsafe {
device.create_graphics_pipelines(
vk::PipelineCache::null(),
std::slice::from_ref(&gpci),
None,
)
}
.map_err(|(_, e)| format!("pipeline: {e:?}"))?; .map_err(|(_, e)| format!("pipeline: {e:?}"))?;
unsafe { device.destroy_shader_module(vm, None); device.destroy_shader_module(fm, None); } unsafe {
device.destroy_shader_module(vm, None);
device.destroy_shader_module(fm, None);
}
let pipeline = pipes[0]; let pipeline = pipes[0];
let framebuffers: Vec<_> = swapchain_image_views.iter() let framebuffers: Vec<_> = swapchain_image_views
.iter()
.map(|&view| { .map(|&view| {
let fci = vk::FramebufferCreateInfo::default() let fci = vk::FramebufferCreateInfo::default()
.render_pass(render_pass).attachments(std::slice::from_ref(&view)) .render_pass(render_pass)
.width(extent.width).height(extent.height).layers(1); .attachments(std::slice::from_ref(&view))
.width(extent.width)
.height(extent.height)
.layers(1);
unsafe { device.create_framebuffer(&fci, None).expect("fb") } unsafe { device.create_framebuffer(&fci, None).expect("fb") }
}).collect(); })
.collect();
let command_pool = { let command_pool = {
let cpci = vk::CommandPoolCreateInfo::default().queue_family_index(qf).flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); let cpci = vk::CommandPoolCreateInfo::default()
unsafe { device.create_command_pool(&cpci, None) }.map_err(|e| format!("cmd_pool: {e:?}"))? .queue_family_index(qf)
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
unsafe { device.create_command_pool(&cpci, None) }
.map_err(|e| format!("cmd_pool: {e:?}"))?
}; };
let command_buffers = { let command_buffers = {
let cai = vk::CommandBufferAllocateInfo::default().command_pool(command_pool).level(vk::CommandBufferLevel::PRIMARY).command_buffer_count(framebuffers.len() as u32); let cai = vk::CommandBufferAllocateInfo::default()
unsafe { device.allocate_command_buffers(&cai) }.map_err(|e| format!("cmd_bufs: {e:?}"))? .command_pool(command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(framebuffers.len() as u32);
unsafe { device.allocate_command_buffers(&cai) }
.map_err(|e| format!("cmd_bufs: {e:?}"))?
}; };
let mut image_available = Vec::new(); let mut image_available = Vec::new();
@@ -242,9 +389,24 @@ impl GraphicsRenderer {
let mut in_flight = Vec::new(); let mut in_flight = Vec::new();
for _ in 0..MAX_FRAMES { for _ in 0..MAX_FRAMES {
unsafe { unsafe {
image_available.push(device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None).map_err(|e| format!("sem: {e:?}"))?); image_available.push(
render_finished.push(device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None).map_err(|e| format!("sem: {e:?}"))?); device
in_flight.push(device.create_fence(&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), None).map_err(|e| format!("fence: {e:?}"))?); .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
.map_err(|e| format!("sem: {e:?}"))?,
);
render_finished.push(
device
.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
.map_err(|e| format!("sem: {e:?}"))?,
);
in_flight.push(
device
.create_fence(
&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED),
None,
)
.map_err(|e| format!("fence: {e:?}"))?,
);
} }
} }
@@ -252,20 +414,41 @@ impl GraphicsRenderer {
let find_mem = |filter: u32, props: vk::MemoryPropertyFlags| -> Result<u32, String> { let find_mem = |filter: u32, props: vk::MemoryPropertyFlags| -> Result<u32, String> {
let mp = unsafe { instance.get_physical_device_memory_properties(physical_device) }; let mp = unsafe { instance.get_physical_device_memory_properties(physical_device) };
for (i, mt) in mp.memory_types.iter().enumerate() { for (i, mt) in mp.memory_types.iter().enumerate() {
if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) { return Ok(i as u32); } if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) {
return Ok(i as u32);
}
} }
Err("No memory type".to_string()) Err("No memory type".to_string())
}; };
let make_buf = |data: &[u8], usage: vk::BufferUsageFlags| -> Result<(vk::Buffer, vk::DeviceMemory), String> { let make_buf = |data: &[u8],
usage: vk::BufferUsageFlags|
-> Result<(vk::Buffer, vk::DeviceMemory), String> {
let sz = data.len() as vk::DeviceSize; let sz = data.len() as vk::DeviceSize;
let bi = vk::BufferCreateInfo::default().size(sz).usage(usage).sharing_mode(vk::SharingMode::EXCLUSIVE); let bi = vk::BufferCreateInfo::default()
let buf = unsafe { device.create_buffer(&bi, None) }.map_err(|e| format!("buf: {e:?}"))?; .size(sz)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buf =
unsafe { device.create_buffer(&bi, None) }.map_err(|e| format!("buf: {e:?}"))?;
let req = unsafe { device.get_buffer_memory_requirements(buf) }; let req = unsafe { device.get_buffer_memory_requirements(buf) };
let mt = find_mem(req.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT)?; let mt = find_mem(
let mem = unsafe { device.allocate_memory(&vk::MemoryAllocateInfo::default().allocation_size(req.size).memory_type_index(mt), None) }.map_err(|e| format!("mem: {e:?}"))?; req.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
let mem = unsafe {
device.allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(req.size)
.memory_type_index(mt),
None,
)
}
.map_err(|e| format!("mem: {e:?}"))?;
unsafe { unsafe {
device.bind_buffer_memory(buf, mem, 0).expect("bind"); device.bind_buffer_memory(buf, mem, 0).expect("bind");
let ptr = device.map_memory(mem, 0, sz, vk::MemoryMapFlags::default()).expect("map"); let ptr = device
.map_memory(mem, 0, sz, vk::MemoryMapFlags::default())
.expect("map");
std::ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len()); std::ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len());
device.unmap_memory(mem); device.unmap_memory(mem);
} }
@@ -273,49 +456,101 @@ impl GraphicsRenderer {
}; };
let verts: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]; let verts: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
let indices: [u16; 6] = [0, 1, 2, 1, 3, 2]; let indices: [u16; 6] = [0, 1, 2, 1, 3, 2];
let (vertex_buffer, vertex_memory) = make_buf(bytemuck::cast_slice(&verts), vk::BufferUsageFlags::VERTEX_BUFFER)?; let (vertex_buffer, vertex_memory) = make_buf(
let (index_buffer, index_memory) = make_buf(bytemuck::cast_slice(&indices), vk::BufferUsageFlags::INDEX_BUFFER)?; bytemuck::cast_slice(&verts),
vk::BufferUsageFlags::VERTEX_BUFFER,
)?;
let (index_buffer, index_memory) = make_buf(
bytemuck::cast_slice(&indices),
vk::BufferUsageFlags::INDEX_BUFFER,
)?;
// Instance buffer // Instance buffer
let instance_count = grid_w * grid_h; let instance_count = grid_w * grid_h;
let inst_sz = (instance_count * std::mem::size_of::<ColorInstance>()) as vk::DeviceSize; let inst_sz = (instance_count * std::mem::size_of::<ColorInstance>()) as vk::DeviceSize;
let ibi = vk::BufferCreateInfo::default().size(inst_sz).usage(vk::BufferUsageFlags::VERTEX_BUFFER).sharing_mode(vk::SharingMode::EXCLUSIVE); let ibi = vk::BufferCreateInfo::default()
let instance_buffer = unsafe { device.create_buffer(&ibi, None) }.map_err(|e| format!("inst buf: {e:?}"))?; .size(inst_sz)
.usage(vk::BufferUsageFlags::VERTEX_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let instance_buffer =
unsafe { device.create_buffer(&ibi, None) }.map_err(|e| format!("inst buf: {e:?}"))?;
let ireq = unsafe { device.get_buffer_memory_requirements(instance_buffer) }; let ireq = unsafe { device.get_buffer_memory_requirements(instance_buffer) };
let imt = find_mem(ireq.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT)?; let imt = find_mem(
let instance_memory = unsafe { device.allocate_memory(&vk::MemoryAllocateInfo::default().allocation_size(ireq.size).memory_type_index(imt), None) }.map_err(|e| format!("inst mem: {e:?}"))?; ireq.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
let instance_memory = unsafe {
device.allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(ireq.size)
.memory_type_index(imt),
None,
)
}
.map_err(|e| format!("inst mem: {e:?}"))?;
let instance_ptr = unsafe { let instance_ptr = unsafe {
device.bind_buffer_memory(instance_buffer, instance_memory, 0).expect("bind inst"); device
let ptr = device.map_memory(instance_memory, 0, inst_sz, vk::MemoryMapFlags::default()).expect("map inst"); .bind_buffer_memory(instance_buffer, instance_memory, 0)
.expect("bind inst");
let ptr = device
.map_memory(instance_memory, 0, inst_sz, vk::MemoryMapFlags::default())
.expect("map inst");
ptr as *mut ColorInstance ptr as *mut ColorInstance
}; };
Ok(Self { Ok(Self {
grid_w, grid_h, grid_w,
entry, instance, surface, physical_device, device, graphics_queue, grid_h,
swapchain_loader, swapchain, swapchain_image_views, swapchain_extent: extent, entry,
render_pass, pipeline, pipeline_layout, framebuffers, instance,
command_pool, command_buffers, surface,
image_available, render_finished, in_flight, frame_index: 0, physical_device,
vertex_buffer, vertex_memory, index_buffer, index_memory, device,
instance_buffer, instance_memory, instance_ptr, instance_count, graphics_queue,
swapchain_loader,
swapchain,
swapchain_image_views,
swapchain_extent: extent,
render_pass,
pipeline,
pipeline_layout,
framebuffers,
command_pool,
command_buffers,
image_available,
render_finished,
in_flight,
frame_index: 0,
vertex_buffer,
vertex_memory,
index_buffer,
index_memory,
instance_buffer,
instance_memory,
instance_ptr,
instance_count,
window, window,
}) })
} }
pub fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) { pub fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) {
self.check_resize(); self.check_resize();
let mut entity_map: std::collections::HashMap<(i32, i32), [u8; 4]> = std::collections::HashMap::new(); let mut entity_map: std::collections::HashMap<(i32, i32), [u8; 4]> =
std::collections::HashMap::new();
for e in entities.all() { for e in entities.all() {
for b in &e.bodies { for b in &e.bodies {
if !b.alive { continue; } if !b.alive {
continue;
}
let sx = b.x as i32 - cam_x; let sx = b.x as i32 - cam_x;
let sy = b.y as i32 - cam_y; let sy = b.y as i32 - cam_y;
if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 { if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 {
let fg = if e.on_fire { [255, 160, 40, 255] } let fg = if e.on_fire {
else if !e.alive { [100, 60, 60, 255] } [255, 160, 40, 255]
else { } else if !e.alive {
[100, 60, 60, 255]
} else {
match e.kind { match e.kind {
EntityKind::Player => [255, 255, 100, 255], EntityKind::Player => [255, 255, 100, 255],
EntityKind::Goblin => [100, 220, 100, 255], EntityKind::Goblin => [100, 220, 100, 255],
@@ -327,7 +562,8 @@ impl GraphicsRenderer {
} }
} }
let instances = unsafe { std::slice::from_raw_parts_mut(self.instance_ptr, self.instance_count) }; let instances =
unsafe { std::slice::from_raw_parts_mut(self.instance_ptr, self.instance_count) };
let bg_default = [10u8, 10, 15, 255]; let bg_default = [10u8, 10, 15, 255];
for dy in 0..self.grid_h { for dy in 0..self.grid_h {
@@ -370,10 +606,16 @@ impl GraphicsRenderer {
let _ = device.reset_fences(&[self.in_flight[frame]]); let _ = device.reset_fences(&[self.in_flight[frame]]);
let image_index = match self.swapchain_loader.acquire_next_image( let image_index = match self.swapchain_loader.acquire_next_image(
self.swapchain, u64::MAX, self.image_available[frame], vk::Fence::null() self.swapchain,
u64::MAX,
self.image_available[frame],
vk::Fence::null(),
) { ) {
Ok((idx, _)) => idx as usize, Ok((idx, _)) => idx as usize,
Err(e) => { eprintln!("acquire: {e:?}"); return; } Err(e) => {
eprintln!("acquire: {e:?}");
return;
}
}; };
let cmd = self.command_buffers[frame]; let cmd = self.command_buffers[frame];
@@ -381,36 +623,59 @@ impl GraphicsRenderer {
let _ = device.begin_command_buffer(cmd, &vk::CommandBufferBeginInfo::default()); let _ = device.begin_command_buffer(cmd, &vk::CommandBufferBeginInfo::default());
let clear = vk::ClearValue { let clear = vk::ClearValue {
color: vk::ClearColorValue { float32: [10.0/255.0, 10.0/255.0, 15.0/255.0, 1.0] }, color: vk::ClearColorValue {
float32: [10.0 / 255.0, 10.0 / 255.0, 15.0 / 255.0, 1.0],
},
}; };
let rp_info = vk::RenderPassBeginInfo::default() let rp_info = vk::RenderPassBeginInfo::default()
.render_pass(self.render_pass) .render_pass(self.render_pass)
.framebuffer(self.framebuffers[image_index]) .framebuffer(self.framebuffers[image_index])
.render_area(vk::Rect2D { offset: vk::Offset2D::default(), extent: self.swapchain_extent }) .render_area(vk::Rect2D {
offset: vk::Offset2D::default(),
extent: self.swapchain_extent,
})
.clear_values(std::slice::from_ref(&clear)); .clear_values(std::slice::from_ref(&clear));
device.cmd_begin_render_pass(cmd, &rp_info, vk::SubpassContents::INLINE); device.cmd_begin_render_pass(cmd, &rp_info, vk::SubpassContents::INLINE);
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline);
let viewport = vk::Viewport { let viewport = vk::Viewport {
x: 0.0, y: 0.0, x: 0.0,
y: 0.0,
width: self.swapchain_extent.width as f32, width: self.swapchain_extent.width as f32,
height: self.swapchain_extent.height as f32, height: self.swapchain_extent.height as f32,
min_depth: 0.0, max_depth: 1.0, min_depth: 0.0,
max_depth: 1.0,
};
let scissor = vk::Rect2D {
offset: vk::Offset2D::default(),
extent: self.swapchain_extent,
}; };
let scissor = vk::Rect2D { offset: vk::Offset2D::default(), extent: self.swapchain_extent };
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&viewport)); device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&viewport));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor)); device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
device.cmd_bind_vertex_buffers(cmd, 0, &[self.vertex_buffer, self.instance_buffer], &[0, 0]); device.cmd_bind_vertex_buffers(
cmd,
0,
&[self.vertex_buffer, self.instance_buffer],
&[0, 0],
);
device.cmd_bind_index_buffer(cmd, self.index_buffer, 0, vk::IndexType::UINT16); device.cmd_bind_index_buffer(cmd, self.index_buffer, 0, vk::IndexType::UINT16);
let pc = PushConstants { let pc = PushConstants {
screen_size: [self.swapchain_extent.width as f32, self.swapchain_extent.height as f32], screen_size: [
self.swapchain_extent.width as f32,
self.swapchain_extent.height as f32,
],
cell_size: [CHAR_W as f32, CHAR_H as f32], cell_size: [CHAR_W as f32, CHAR_H as f32],
}; };
device.cmd_push_constants(cmd, self.pipeline_layout, vk::ShaderStageFlags::VERTEX, device.cmd_push_constants(
0, bytemuck::bytes_of(&pc)); cmd,
self.pipeline_layout,
vk::ShaderStageFlags::VERTEX,
0,
bytemuck::bytes_of(&pc),
);
device.cmd_draw_indexed(cmd, 6, self.instance_count as u32, 0, 0, 0); device.cmd_draw_indexed(cmd, 6, self.instance_count as u32, 0, 0, 0);
device.cmd_end_render_pass(cmd); device.cmd_end_render_pass(cmd);
@@ -422,25 +687,37 @@ impl GraphicsRenderer {
.wait_dst_stage_mask(&wait_stages) .wait_dst_stage_mask(&wait_stages)
.command_buffers(std::slice::from_ref(&cmd)) .command_buffers(std::slice::from_ref(&cmd))
.signal_semaphores(std::slice::from_ref(&self.render_finished[frame])); .signal_semaphores(std::slice::from_ref(&self.render_finished[frame]));
let _ = device.queue_submit(self.graphics_queue, std::slice::from_ref(&submit_info), self.in_flight[frame]); let _ = device.queue_submit(
self.graphics_queue,
std::slice::from_ref(&submit_info),
self.in_flight[frame],
);
let img_idx = image_index as u32; let img_idx = image_index as u32;
let present_info = vk::PresentInfoKHR::default() let present_info = vk::PresentInfoKHR::default()
.wait_semaphores(std::slice::from_ref(&self.render_finished[frame])) .wait_semaphores(std::slice::from_ref(&self.render_finished[frame]))
.swapchains(std::slice::from_ref(&self.swapchain)) .swapchains(std::slice::from_ref(&self.swapchain))
.image_indices(std::slice::from_ref(&img_idx)); .image_indices(std::slice::from_ref(&img_idx));
let _ = self.swapchain_loader.queue_present(self.graphics_queue, &present_info); let _ = self
.swapchain_loader
.queue_present(self.graphics_queue, &present_info);
} }
self.frame_index = (self.frame_index + 1) % MAX_FRAMES; self.frame_index = (self.frame_index + 1) % MAX_FRAMES;
} }
pub fn grid_w(&self) -> usize { self.grid_w } pub fn grid_w(&self) -> usize {
pub fn grid_h(&self) -> usize { self.grid_h } self.grid_w
}
pub fn grid_h(&self) -> usize {
self.grid_h
}
fn check_resize(&mut self) { fn check_resize(&mut self) {
let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance); let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance);
let caps = match unsafe { sl.get_physical_device_surface_capabilities(self.physical_device, self.surface) } { let caps = match unsafe {
sl.get_physical_device_surface_capabilities(self.physical_device, self.surface)
} {
Ok(c) => c, Ok(c) => c,
Err(_) => return, Err(_) => return,
}; };
@@ -457,7 +734,8 @@ impl GraphicsRenderer {
}; };
if new_extent.width == self.swapchain_extent.width if new_extent.width == self.swapchain_extent.width
&& new_extent.height == self.swapchain_extent.height { && new_extent.height == self.swapchain_extent.height
{
return; return;
} }
@@ -465,11 +743,21 @@ impl GraphicsRenderer {
return; return;
} }
unsafe { let _ = self.device.device_wait_idle(); } unsafe {
let _ = self.device.device_wait_idle();
}
// Destroy old swapchain resources // Destroy old swapchain resources
for &fb in &self.framebuffers { unsafe { self.device.destroy_framebuffer(fb, None); } } for &fb in &self.framebuffers {
for &v in &self.swapchain_image_views { unsafe { self.device.destroy_image_view(v, None); } } unsafe {
self.device.destroy_framebuffer(fb, None);
}
}
for &v in &self.swapchain_image_views {
unsafe {
self.device.destroy_image_view(v, None);
}
}
// Recreate swapchain // Recreate swapchain
let sci = vk::SwapchainCreateInfoKHR::default() let sci = vk::SwapchainCreateInfoKHR::default()
@@ -492,40 +780,61 @@ impl GraphicsRenderer {
Err(_) => return, Err(_) => return,
}; };
let new_images = match unsafe { self.swapchain_loader.get_swapchain_images(new_swapchain) } { let new_images = match unsafe { self.swapchain_loader.get_swapchain_images(new_swapchain) }
{
Ok(i) => i, Ok(i) => i,
Err(_) => return, Err(_) => return,
}; };
let new_views: Vec<_> = new_images.iter() let new_views: Vec<_> = new_images
.iter()
.map(|&img| { .map(|&img| {
let vi = vk::ImageViewCreateInfo::default() let vi = vk::ImageViewCreateInfo::default()
.image(img).view_type(vk::ImageViewType::TYPE_2D) .image(img)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM) .format(vk::Format::B8G8R8A8_UNORM)
.subresource_range(vk::ImageSubresourceRange { .subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR, aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0, level_count: 1, base_mip_level: 0,
base_array_layer: 0, layer_count: 1, level_count: 1,
base_array_layer: 0,
layer_count: 1,
}); });
unsafe { self.device.create_image_view(&vi, None).expect("image_view") } unsafe {
}).collect(); self.device
.create_image_view(&vi, None)
.expect("image_view")
}
})
.collect();
let new_framebuffers: Vec<_> = new_views.iter() let new_framebuffers: Vec<_> = new_views
.iter()
.map(|&view| { .map(|&view| {
let fci = vk::FramebufferCreateInfo::default() let fci = vk::FramebufferCreateInfo::default()
.render_pass(self.render_pass) .render_pass(self.render_pass)
.attachments(std::slice::from_ref(&view)) .attachments(std::slice::from_ref(&view))
.width(new_extent.width).height(new_extent.height).layers(1); .width(new_extent.width)
.height(new_extent.height)
.layers(1);
unsafe { self.device.create_framebuffer(&fci, None).expect("fb") } unsafe { self.device.create_framebuffer(&fci, None).expect("fb") }
}).collect(); })
.collect();
// Reallocate command buffers for new count // Reallocate command buffers for new count
unsafe { self.device.free_command_buffers(self.command_pool, &self.command_buffers); } unsafe {
self.device
.free_command_buffers(self.command_pool, &self.command_buffers);
}
let cai = vk::CommandBufferAllocateInfo::default() let cai = vk::CommandBufferAllocateInfo::default()
.command_pool(self.command_pool) .command_pool(self.command_pool)
.level(vk::CommandBufferLevel::PRIMARY) .level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(new_framebuffers.len() as u32); .command_buffer_count(new_framebuffers.len() as u32);
let new_cmd_bufs = unsafe { self.device.allocate_command_buffers(&cai).expect("cmd_bufs") }; let new_cmd_bufs = unsafe {
self.device
.allocate_command_buffers(&cai)
.expect("cmd_bufs")
};
// Reallocate instance buffer if grid size changed // Reallocate instance buffer if grid size changed
let new_grid_w = (new_extent.width / CHAR_W) as usize; let new_grid_w = (new_extent.width / CHAR_W) as usize;
@@ -544,30 +853,61 @@ impl GraphicsRenderer {
.size(inst_sz) .size(inst_sz)
.usage(vk::BufferUsageFlags::VERTEX_BUFFER) .usage(vk::BufferUsageFlags::VERTEX_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE); .sharing_mode(vk::SharingMode::EXCLUSIVE);
self.instance_buffer = unsafe { self.device.create_buffer(&ibi, None) }.expect("inst buf"); self.instance_buffer =
let ireq = unsafe { self.device.get_buffer_memory_requirements(self.instance_buffer) }; unsafe { self.device.create_buffer(&ibi, None) }.expect("inst buf");
let ireq = unsafe {
self.device
.get_buffer_memory_requirements(self.instance_buffer)
};
let find_mem = |filter: u32, props: vk::MemoryPropertyFlags| -> u32 { let find_mem = |filter: u32, props: vk::MemoryPropertyFlags| -> u32 {
let mp = unsafe { self.instance.get_physical_device_memory_properties(self.physical_device) }; let mp = unsafe {
self.instance
.get_physical_device_memory_properties(self.physical_device)
};
for (i, mt) in mp.memory_types.iter().enumerate() { for (i, mt) in mp.memory_types.iter().enumerate() {
if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) { return i as u32; } if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) {
return i as u32;
}
} }
0 0
}; };
let imt = find_mem(ireq.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT); let imt = find_mem(
self.instance_memory = unsafe { self.device.allocate_memory( ireq.memory_type_bits,
&vk::MemoryAllocateInfo::default().allocation_size(ireq.size).memory_type_index(imt), None) vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
}.expect("inst mem"); );
self.instance_memory = unsafe {
self.device.allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(ireq.size)
.memory_type_index(imt),
None,
)
}
.expect("inst mem");
self.instance_ptr = unsafe { self.instance_ptr = unsafe {
self.device.bind_buffer_memory(self.instance_buffer, self.instance_memory, 0).expect("bind"); self.device
let ptr = self.device.map_memory(self.instance_memory, 0, inst_sz, vk::MemoryMapFlags::default()).expect("map"); .bind_buffer_memory(self.instance_buffer, self.instance_memory, 0)
.expect("bind");
let ptr = self
.device
.map_memory(
self.instance_memory,
0,
inst_sz,
vk::MemoryMapFlags::default(),
)
.expect("map");
ptr as *mut ColorInstance ptr as *mut ColorInstance
}; };
self.instance_count = new_count; self.instance_count = new_count;
} }
// Destroy old swapchain // Destroy old swapchain
unsafe { self.swapchain_loader.destroy_swapchain(self.swapchain, None); } unsafe {
self.swapchain_loader
.destroy_swapchain(self.swapchain, None);
}
self.swapchain = new_swapchain; self.swapchain = new_swapchain;
self.swapchain_image_views = new_views; self.swapchain_image_views = new_views;
@@ -583,13 +923,22 @@ impl Drop for GraphicsRenderer {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let _ = self.device.device_wait_idle(); let _ = self.device.device_wait_idle();
for &f in &self.in_flight { self.device.destroy_fence(f, None); } for &f in &self.in_flight {
for &s in &self.image_available { self.device.destroy_semaphore(s, None); } self.device.destroy_fence(f, None);
for &s in &self.render_finished { self.device.destroy_semaphore(s, None); } }
for &s in &self.image_available {
self.device.destroy_semaphore(s, None);
}
for &s in &self.render_finished {
self.device.destroy_semaphore(s, None);
}
self.device.destroy_command_pool(self.command_pool, None); self.device.destroy_command_pool(self.command_pool, None);
for &fb in &self.framebuffers { self.device.destroy_framebuffer(fb, None); } for &fb in &self.framebuffers {
self.device.destroy_framebuffer(fb, None);
}
self.device.destroy_pipeline(self.pipeline, None); self.device.destroy_pipeline(self.pipeline, None);
self.device.destroy_pipeline_layout(self.pipeline_layout, None); self.device
.destroy_pipeline_layout(self.pipeline_layout, None);
self.device.destroy_render_pass(self.render_pass, None); self.device.destroy_render_pass(self.render_pass, None);
self.device.destroy_buffer(self.instance_buffer, None); self.device.destroy_buffer(self.instance_buffer, None);
self.device.free_memory(self.instance_memory, None); self.device.free_memory(self.instance_memory, None);
@@ -597,8 +946,11 @@ impl Drop for GraphicsRenderer {
self.device.free_memory(self.vertex_memory, None); self.device.free_memory(self.vertex_memory, None);
self.device.destroy_buffer(self.index_buffer, None); self.device.destroy_buffer(self.index_buffer, None);
self.device.free_memory(self.index_memory, None); self.device.free_memory(self.index_memory, None);
for &v in &self.swapchain_image_views { self.device.destroy_image_view(v, None); } for &v in &self.swapchain_image_views {
self.swapchain_loader.destroy_swapchain(self.swapchain, None); self.device.destroy_image_view(v, None);
}
self.swapchain_loader
.destroy_swapchain(self.swapchain, None);
let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance); let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance);
sl.destroy_surface(self.surface, None); sl.destroy_surface(self.surface, None);
self.device.destroy_device(None); self.device.destroy_device(None);
+837 -233
View File
File diff suppressed because it is too large Load Diff