commit beed1145087123f9d2ded6b2b2d7e62435ef4ea8 Author: Leonid Pershin Date: Thu Jun 11 05:33:10 2026 +0300 Initial LittleSim scaffold on the mrgameeng engine submodule God-game: minimal graphics, deep simulation. The engine lives in the engine/ git submodule and its sources plus test suites are part of LittleSim.sln, so engine and game are developed in one editor window. Playable skeleton: seed-deterministic terrain (smoothed height field -> water/sand/grass/forest/mountain tinted cells), 80 wandering villagers on a Y-sorted layer, god camera (WASD pan, wheel zoom, world-bounds clamp), HUD and the dev console with a 'regen [seed]' command. CLAUDE.md sets the game rules: simulation-first, one-seed determinism, simulation/presentation split, a console command for every mechanic. Design docs in Russian under docs/. Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e2e97c --- /dev/null +++ b/.gitignore @@ -0,0 +1,416 @@ +# ---> VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +# ---> VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..46fe3e5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "engine"] + path = engine + url = https://gitea.hsrv.site/mrleo1nid/mrgameeng.git diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9f1064f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,43 @@ +# LittleSim + +A god-game: minimal graphics, deep simulation. Built on the **mrgameeng** engine +(MonoGame + Friflo ECS), vendored as the git submodule `engine/` so the game and the +engine are developed side by side in one editor window. + +Game design docs live in `docs/` and are written in **Russian**. Engine rules live in +`engine/CLAUDE.md` — read it before touching engine code; both rule sets apply here. + +## Layout + +``` +engine/ mrgameeng git submodule (own repo, own CLAUDE.md) +src/LittleSim the game (net8.0); references engine projects directly +docs/ концепт, симуляция, roadmap (Russian) +LittleSim.sln game + engine sources + engine tests — one window for everything +``` + +## Commands + +``` +git submodule update --init # after fresh clone +dotnet build LittleSim.sln +dotnet run --project src/LittleSim -c Release # measure perf in Release only +dotnet test LittleSim.sln # runs the engine test suites +``` + +## Submodule workflow + +Engine changes are committed **inside `engine/` first** (engine repo, its commit style), +pushed to the engine remote, then the submodule pointer bump is committed here. +Never commit a pointer to an unpushed engine commit. + +## Game rules + +- Simulation first: depth of behavior over visuals. Graphics stay primitive + (tinted quads are fine); complexity budget goes to simulation systems. +- Determinism: the whole world derives from one integer seed. Systems that need + randomness own a seeded `Random`; never use `Random.Shared` inside simulation. +- Simulation/presentation split: simulation systems mutate components only; + rendering reads them. No draw calls or UI from simulation systems. +- ECS-first per the engine: plain `struct : IComponent` data, logic in systems. +- Every new mechanic gets a dev-console command for testing (`regen`, `timescale`, …). diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..42c7999 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + + latest + enable + enable + true + true + $(MSBuildProjectName) + + + diff --git a/LittleSim.sln b/LittleSim.sln new file mode 100644 index 0000000..962fbef --- /dev/null +++ b/LittleSim.sln @@ -0,0 +1,242 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LittleSim", "src\LittleSim\LittleSim.csproj", "{B8723791-CCB6-4C57-9A98-D00F01EA931C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "engine", "engine", "{D585295F-7994-3649-1065-7AA403C41681}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Core", "engine\src\MrGameEng.Core\MrGameEng.Core.csproj", "{FE6C04D3-E619-4C77-A971-1357463676B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Graphics", "engine\src\MrGameEng.Graphics\MrGameEng.Graphics.csproj", "{F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Input", "engine\src\MrGameEng.Input\MrGameEng.Input.csproj", "{7D7E001D-72ED-4204-94D5-B8A0F706D544}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Audio", "engine\src\MrGameEng.Audio\MrGameEng.Audio.csproj", "{A2620CBF-3404-4583-AD55-FCC8A704BD9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Assets", "engine\src\MrGameEng.Assets\MrGameEng.Assets.csproj", "{4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.UI", "engine\src\MrGameEng.UI\MrGameEng.UI.csproj", "{A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.DevConsole", "engine\src\MrGameEng.DevConsole\MrGameEng.DevConsole.csproj", "{0C370DCA-18AB-4A79-9C60-BE00E7A57180}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Assets.Generator", "engine\src\MrGameEng.Assets.Generator\MrGameEng.Assets.Generator.csproj", "{BA456A86-0960-45C3-B48E-5882D7A3873F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{CD3B5CE5-C23F-38B4-04AA-A303F94731A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Assets.Generator.Tests", "engine\tests\MrGameEng.Assets.Generator.Tests\MrGameEng.Assets.Generator.Tests.csproj", "{83548DF5-75BC-45EF-8911-83BE5173CD2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Core.Tests", "engine\tests\MrGameEng.Core.Tests\MrGameEng.Core.Tests.csproj", "{2994C301-DFD3-48D1-B44C-F01ED28FBE93}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.DevConsole.Tests", "engine\tests\MrGameEng.DevConsole.Tests\MrGameEng.DevConsole.Tests.csproj", "{A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Graphics.Tests", "engine\tests\MrGameEng.Graphics.Tests\MrGameEng.Graphics.Tests.csproj", "{21B1878F-2532-4D48-95DD-1C7268DCF1D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Input.Tests", "engine\tests\MrGameEng.Input.Tests\MrGameEng.Input.Tests.csproj", "{E9B94072-798E-4E09-BD9E-1FE6165D7EA4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Debug|x64.Build.0 = Debug|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Debug|x86.Build.0 = Debug|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Release|Any CPU.Build.0 = Release|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Release|x64.ActiveCfg = Release|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Release|x64.Build.0 = Release|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Release|x86.ActiveCfg = Release|Any CPU + {B8723791-CCB6-4C57-9A98-D00F01EA931C}.Release|x86.Build.0 = Release|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Debug|x64.Build.0 = Debug|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Debug|x86.Build.0 = Debug|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Release|Any CPU.Build.0 = Release|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Release|x64.ActiveCfg = Release|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Release|x64.Build.0 = Release|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Release|x86.ActiveCfg = Release|Any CPU + {FE6C04D3-E619-4C77-A971-1357463676B7}.Release|x86.Build.0 = Release|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Debug|x64.ActiveCfg = Debug|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Debug|x64.Build.0 = Debug|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Debug|x86.ActiveCfg = Debug|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Debug|x86.Build.0 = Debug|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Release|Any CPU.Build.0 = Release|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Release|x64.ActiveCfg = Release|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Release|x64.Build.0 = Release|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Release|x86.ActiveCfg = Release|Any CPU + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED}.Release|x86.Build.0 = Release|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Debug|x64.ActiveCfg = Debug|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Debug|x64.Build.0 = Debug|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Debug|x86.ActiveCfg = Debug|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Debug|x86.Build.0 = Debug|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Release|Any CPU.Build.0 = Release|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Release|x64.ActiveCfg = Release|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Release|x64.Build.0 = Release|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Release|x86.ActiveCfg = Release|Any CPU + {7D7E001D-72ED-4204-94D5-B8A0F706D544}.Release|x86.Build.0 = Release|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Debug|x64.ActiveCfg = Debug|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Debug|x64.Build.0 = Debug|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Debug|x86.Build.0 = Debug|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Release|Any CPU.Build.0 = Release|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Release|x64.ActiveCfg = Release|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Release|x64.Build.0 = Release|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Release|x86.ActiveCfg = Release|Any CPU + {A2620CBF-3404-4583-AD55-FCC8A704BD9A}.Release|x86.Build.0 = Release|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Debug|x64.Build.0 = Debug|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Debug|x86.Build.0 = Debug|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Release|Any CPU.Build.0 = Release|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Release|x64.ActiveCfg = Release|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Release|x64.Build.0 = Release|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Release|x86.ActiveCfg = Release|Any CPU + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27}.Release|x86.Build.0 = Release|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Debug|x64.ActiveCfg = Debug|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Debug|x64.Build.0 = Debug|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Debug|x86.ActiveCfg = Debug|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Debug|x86.Build.0 = Debug|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Release|Any CPU.Build.0 = Release|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Release|x64.ActiveCfg = Release|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Release|x64.Build.0 = Release|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Release|x86.ActiveCfg = Release|Any CPU + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D}.Release|x86.Build.0 = Release|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Debug|x64.Build.0 = Debug|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Debug|x86.Build.0 = Debug|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Release|Any CPU.Build.0 = Release|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Release|x64.ActiveCfg = Release|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Release|x64.Build.0 = Release|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Release|x86.ActiveCfg = Release|Any CPU + {0C370DCA-18AB-4A79-9C60-BE00E7A57180}.Release|x86.Build.0 = Release|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Debug|x64.ActiveCfg = Debug|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Debug|x64.Build.0 = Debug|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Debug|x86.ActiveCfg = Debug|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Debug|x86.Build.0 = Debug|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Release|Any CPU.Build.0 = Release|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Release|x64.ActiveCfg = Release|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Release|x64.Build.0 = Release|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Release|x86.ActiveCfg = Release|Any CPU + {BA456A86-0960-45C3-B48E-5882D7A3873F}.Release|x86.Build.0 = Release|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Debug|x64.ActiveCfg = Debug|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Debug|x64.Build.0 = Debug|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Debug|x86.ActiveCfg = Debug|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Debug|x86.Build.0 = Debug|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Release|Any CPU.Build.0 = Release|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Release|x64.ActiveCfg = Release|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Release|x64.Build.0 = Release|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Release|x86.ActiveCfg = Release|Any CPU + {83548DF5-75BC-45EF-8911-83BE5173CD2D}.Release|x86.Build.0 = Release|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Debug|x64.ActiveCfg = Debug|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Debug|x64.Build.0 = Debug|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Debug|x86.ActiveCfg = Debug|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Debug|x86.Build.0 = Debug|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Release|Any CPU.Build.0 = Release|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Release|x64.ActiveCfg = Release|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Release|x64.Build.0 = Release|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Release|x86.ActiveCfg = Release|Any CPU + {2994C301-DFD3-48D1-B44C-F01ED28FBE93}.Release|x86.Build.0 = Release|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Debug|x64.ActiveCfg = Debug|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Debug|x64.Build.0 = Debug|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Debug|x86.ActiveCfg = Debug|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Debug|x86.Build.0 = Debug|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Release|Any CPU.Build.0 = Release|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Release|x64.ActiveCfg = Release|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Release|x64.Build.0 = Release|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Release|x86.ActiveCfg = Release|Any CPU + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C}.Release|x86.Build.0 = Release|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Debug|x64.ActiveCfg = Debug|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Debug|x64.Build.0 = Debug|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Debug|x86.ActiveCfg = Debug|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Debug|x86.Build.0 = Debug|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Release|Any CPU.Build.0 = Release|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Release|x64.ActiveCfg = Release|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Release|x64.Build.0 = Release|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Release|x86.ActiveCfg = Release|Any CPU + {21B1878F-2532-4D48-95DD-1C7268DCF1D5}.Release|x86.Build.0 = Release|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Debug|x64.Build.0 = Debug|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Debug|x86.Build.0 = Debug|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Release|Any CPU.Build.0 = Release|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Release|x64.ActiveCfg = Release|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Release|x64.Build.0 = Release|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Release|x86.ActiveCfg = Release|Any CPU + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B8723791-CCB6-4C57-9A98-D00F01EA931C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} = {D585295F-7994-3649-1065-7AA403C41681} + {FE6C04D3-E619-4C77-A971-1357463676B7} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {F35A5F1A-9A1B-4F57-BC81-BF16BC574DED} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {7D7E001D-72ED-4204-94D5-B8A0F706D544} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {A2620CBF-3404-4583-AD55-FCC8A704BD9A} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {4FCD860B-F8E1-42F5-8A53-EA9AFCCE5A27} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {A90690FB-E3A8-4C92-94E2-33D8E1F27D9D} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {0C370DCA-18AB-4A79-9C60-BE00E7A57180} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {BA456A86-0960-45C3-B48E-5882D7A3873F} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519} + {CD3B5CE5-C23F-38B4-04AA-A303F94731A5} = {D585295F-7994-3649-1065-7AA403C41681} + {83548DF5-75BC-45EF-8911-83BE5173CD2D} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5} + {2994C301-DFD3-48D1-B44C-F01ED28FBE93} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5} + {A3C128F7-6CCD-46CD-B935-C30E15EC7E1C} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5} + {21B1878F-2532-4D48-95DD-1C7268DCF1D5} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5} + {E9B94072-798E-4E09-BD9E-1FE6165D7EA4} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..d215c99 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# LittleSim + +A god-game with minimal graphics and deep simulation, built on the +[mrgameeng](https://gitea.hsrv.site/mrleo1nid/mrgameeng) engine (git submodule `engine/`). + +``` +git clone --recurse-submodules +dotnet run --project src/LittleSim -c Release +``` + +Design docs (Russian): [docs/concept.md](docs/concept.md), [docs/roadmap.md](docs/roadmap.md). diff --git a/docs/concept.md b/docs/concept.md new file mode 100644 index 0000000..1f910d5 --- /dev/null +++ b/docs/concept.md @@ -0,0 +1,42 @@ +# LittleSim — концепт + +## Видение + +**LittleSim** — симулятор бога: игрок наблюдает за маленьким живым миром и вмешивается +в него божественными силами. Графика нарочно простая (тонированные квадраты, никакого +арта на старте) — вся сложность уходит в **глубину симуляции**. Идеал: мир, за которым +интересно просто наблюдать, как за муравейником, а каждое вмешательство игрока даёт +каскад непредсказуемых, но объяснимых последствий. + +## Столпы + +1. **Симуляция важнее картинки.** Любой выбор «красивее или глубже» решается в пользу глубже. +2. **Эмерджентность.** Поведение мира складывается из простых правил отдельных систем, + а не скриптуется. Голод + неурожай → миграция; не «событие миграции» по таймеру. +3. **Детерминизм.** Весь мир выводится из одного целого сида. Один сид = один мир = + воспроизводимый баг-репорт. +4. **Наблюдаемость.** Всё, что симулируется, можно увидеть: через визуальное состояние + мира, HUD, дев-консоль. Скрытая симуляция — несуществующая симуляция. + +## Мир + +- Клеточный рельеф: вода, песок, трава, лес, горы (высотная карта по сиду). +- Жители — автономные агенты с потребностями (еда, сон, безопасность, общение), + которые сами решают, что делать (utility-подход: действие = максимум полезности). +- Ресурсы и экология: еда растёт и истощается, лес вырубается и восстанавливается. +- Время: день/ночь, сезоны; `timescale` управляет скоростью наблюдения. + +## Роль игрока (бог) + +Игрок не управляет жителями напрямую — только воздействует на мир: +- терраформинг (поднять/опустить землю, вода); +- климат и катаклизмы (дождь, засуха, молния); +- благословения/проклятия отдельных жителей или земель. + +Вера жителей — ресурс бога: вмешательства тратят её, впечатляющие результаты пополняют. + +## Технология + +Движок — mrgameeng (сабмодуль `engine/`): ECS Friflo, свой батчер-рендерер, +ассеты без пайплайна, Myra UI, дев-консоль. Правила разработки — в CLAUDE.md +(корень и engine/). diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..2f33d27 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,36 @@ +# Roadmap LittleSim + +Этап закрыт, когда механика работает, наблюдаема (HUD/консоль/визуал) +и управляется командой консоли. + +## Этап 0 — Каркас ✅ +- [x] Репозиторий с движком-сабмодулем, общий solution +- [x] Генерация рельефа по сиду (вода/песок/трава/лес/горы) +- [x] Жители-заглушки (блуждание), камера бога, HUD, консоль (`regen`) + +## Этап 1 — Жители как агенты +- [ ] Потребности: голод, усталость (тикают со временем) +- [ ] Utility-выбор действия: искать еду / отдыхать / бродить +- [ ] Еда на траве/в лесу: растёт, собирается, истощается +- [ ] Смерть от голода; визуальное состояние жителя (цвет/прозрачность) +- [ ] Консоль: `spawn `, `feed`, `starve` для тестов + +## Этап 2 — Общество +- [ ] Дома и поселения (житель несёт еду домой, спит дома) +- [ ] Размножение при сытости; рост населения +- [ ] Простые профессии: собиратель, строитель + +## Этап 3 — Бог +- [ ] Вера как ресурс (генерируется жителями) +- [ ] Первые силы: дождь (ускоряет рост еды), молния (поджигает лес) +- [ ] Терраформинг клеток + +## Этап 4 — Экология и время +- [ ] День/ночь, сезоны +- [ ] Восстановление/деградация ресурсов, пожары +- [ ] Миграции при нехватке ресурсов + +## Бэклог +- Сохранение/загрузка мира (сериализация Friflo) +- Фиксированный тик симуляции, независимый от рендера +- Исторические графики (население, еда) в дев-оверлее diff --git a/engine b/engine new file mode 160000 index 0000000..8d3f478 --- /dev/null +++ b/engine @@ -0,0 +1 @@ +Subproject commit 8d3f478acddd9bdf2ac7b8e9761f15a6a9d5d7e2 diff --git a/src/LittleSim/Assets/Textures/white.png b/src/LittleSim/Assets/Textures/white.png new file mode 100644 index 0000000..4998c1a Binary files /dev/null and b/src/LittleSim/Assets/Textures/white.png differ diff --git a/src/LittleSim/LittleSim.csproj b/src/LittleSim/LittleSim.csproj new file mode 100644 index 0000000..4b7dfbc --- /dev/null +++ b/src/LittleSim/LittleSim.csproj @@ -0,0 +1,25 @@ + + + + WinExe + net8.0 + + + + + + + + + + + + + + + + + + + diff --git a/src/LittleSim/Program.cs b/src/LittleSim/Program.cs new file mode 100644 index 0000000..009fd23 --- /dev/null +++ b/src/LittleSim/Program.cs @@ -0,0 +1,15 @@ +using LittleSim.Scenes; +using Microsoft.Xna.Framework; +using MrGameEng.Core; + +using var host = new GameHost( + new GameHostOptions + { + Title = "LittleSim", + Width = 1280, + Height = 720, + ClearColor = new Color(12, 16, 24), + }, + new WorldScene(seed: 1337)); + +host.Run(); diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs new file mode 100644 index 0000000..54fa2ec --- /dev/null +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -0,0 +1,105 @@ +using Friflo.Engine.ECS; +using LittleSim.Sim; +using Microsoft.Xna.Framework; +using MrGameEng.Assets; +using MrGameEng.Core; +using MrGameEng.DevConsole; +using MrGameEng.Graphics; +using MrGameEng.Input; +using MrGameEng.UI; +using Myra.Graphics2D.UI; + +namespace LittleSim.Scenes; + +/// +/// Мир LittleSim: сгенерированный по сиду рельеф (клетки-спрайты) и жители, бродящие по нему. +/// Камера бога, HUD и консоль с командой "regen [seed]". +/// +public sealed class WorldScene : Scene +{ + public const int WorldWidth = 120; + public const int WorldHeight = 70; + public const int CellSize = 16; + private const int Population = 80; + + private static readonly RectF WorldBounds = new(0f, 0f, WorldWidth * CellSize, WorldHeight * CellSize); + private static LayerId _beingsLayer; + private static bool _layersRegistered; + + private readonly int _seed; + + public WorldScene(int seed) => _seed = seed; + + protected override void OnLoad() + { + var assets = Context.Services.GetOrDefault() ?? Context.UseAssets(); + var input = this.UseInput(); + var renderer = this.UseRenderer2D(new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }); + if (!_layersRegistered) + { + _beingsLayer = renderer.Layers.Register("Beings", LayerSpace.World, LayerSortMode.YSort); + _layersRegistered = true; + } + + var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White)); + + // Рельеф: одна текстура на все клетки → один draw call на слой. + var heights = WorldGenerator.GenerateHeights(WorldWidth, WorldHeight, _seed); + var landCells = new List(); + for (var x = 0; x < WorldWidth; x++) + { + for (var y = 0; y < WorldHeight; y++) + { + var kind = WorldGenerator.Classify(heights[x, y]); + Store.CreateEntity( + Transform2D.At(new Vector2(x * CellSize, y * CellSize)), + new Sprite(white) { Color = WorldGenerator.ColorOf(kind) }); + if (WorldGenerator.IsLand(kind)) + { + landCells.Add(new Point(x, y)); + } + } + } + + // Жители: появляются на суше, бродят (Y-sort слой — кто ниже, тот ближе). + var random = new Random(_seed); + for (var i = 0; i < Population && landCells.Count > 0; i++) + { + var cell = landCells[random.Next(landCells.Count)]; + var sprite = new Sprite(white, _beingsLayer) { Color = new Color(214, 81, 72) }; + sprite.CenterOrigin(); + Store.CreateEntity( + new Transform2D( + new Vector2((cell.X + 0.5f) * CellSize, (cell.Y + 0.5f) * CellSize), + scale: new Vector2(0.45f)), + sprite, + new Wander()); + } + + var camera = Store.CreateEntity(new Camera(WorldBounds.Center, zoom: 1f, bounds: WorldBounds)); + + // HUD и консоль. + var desktop = this.UseUI(); + var hudLabel = new Label { Left = 10, Top = 8 }; + desktop.Root = hudLabel; + + var console = this.UseDevConsole(); + console.Register("regen", "regen [seed] — regenerate the world", (c, args) => + { + if (Context.Scenes.IsTransitioning) + { + return; + } + + var seed = args.Length > 0 ? int.Parse(args[0]) : Random.Shared.Next(); + c.WriteLine($"regenerating world, seed {seed}"); + Context.Scenes.Switch(new WorldScene(seed), Transition.Fade(0.6f)); + }); + + UpdateSystems.Add(new WanderSystem(_seed, WorldBounds)); + UpdateSystems.Add(new GodCameraSystem(camera, input, console)); + UpdateSystems.Add(new HudSystem(Context, hudLabel, _seed, Population)); + + Log.Info($"World generated: seed {_seed}, {WorldWidth}x{WorldHeight} cells, population {Population}"); + } +} diff --git a/src/LittleSim/Sim/SimSystems.cs b/src/LittleSim/Sim/SimSystems.cs new file mode 100644 index 0000000..59f50df --- /dev/null +++ b/src/LittleSim/Sim/SimSystems.cs @@ -0,0 +1,105 @@ +using Friflo.Engine.ECS; +using Friflo.Engine.ECS.Systems; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MrGameEng.Graphics; +using MrGameEng.Input; + +namespace LittleSim.Sim; + +/// Житель бродит: направление меняется по таймеру, движение детерминировано сидом. +public struct Wander : IComponent +{ + public Vector2 Direction; + public float ChangeIn; +} + +/// +/// Блуждание жителей. Один Random на систему, сид от мира — при одинаковом сиде +/// и порядке обновления симуляция воспроизводима. +/// +public sealed class WanderSystem(int seed, RectF worldBounds) : QuerySystem +{ + private const float Speed = 22f; + + private readonly Random _random = new(seed); + + protected override void OnUpdate() + { + var delta = Tick.deltaTime; + foreach (var (transforms, wanders, _) in Query.Chunks) + { + var t = transforms.Span; + var w = wanders.Span; + for (var i = 0; i < t.Length; i++) + { + ref var wander = ref w[i]; + wander.ChangeIn -= delta; + if (wander.ChangeIn <= 0f) + { + var angle = _random.NextSingle() * MathF.Tau; + wander.Direction = new Vector2(MathF.Cos(angle), MathF.Sin(angle)); + wander.ChangeIn = 1f + _random.NextSingle() * 3f; + } + + ref var position = ref t[i].Position; + position += wander.Direction * (Speed * delta); + position.X = Math.Clamp(position.X, worldBounds.Left, worldBounds.Right); + position.Y = Math.Clamp(position.Y, worldBounds.Top, worldBounds.Bottom); + } + } + } +} + +/// Камера бога: WASD/стрелки — панорамирование, колесо — зум. Молчит, пока открыта консоль. +public sealed class GodCameraSystem( + Entity cameraEntity, InputManager input, MrGameEng.DevConsole.DevConsole console) : BaseSystem +{ + protected override void OnUpdateGroup() + { + if (console.IsOpen) + { + return; + } + + ref var camera = ref cameraEntity.GetComponent(); + + var pan = new Vector2( + Axis(input, Keys.A, Keys.Left, Keys.D, Keys.Right), + Axis(input, Keys.W, Keys.Up, Keys.S, Keys.Down)); + if (pan != Vector2.Zero) + { + pan.Normalize(); + camera.Position += pan * (500f / camera.Zoom) * Tick.deltaTime; + } + + camera.Zoom = Math.Clamp(camera.Zoom * (1f + input.WheelDelta * 0.001f), 0.4f, 8f); + } + + private static float Axis(InputManager input, Keys negativeA, Keys negativeB, Keys positiveA, Keys positiveB) => + (input.IsKeyDown(positiveA) || input.IsKeyDown(positiveB) ? 1f : 0f) - + (input.IsKeyDown(negativeA) || input.IsKeyDown(negativeB) ? 1f : 0f); +} + +/// Строка статуса в HUD: сид, население, FPS (обновляется 4 раза в секунду). +public sealed class HudSystem( + MrGameEng.Core.EngineContext context, Myra.Graphics2D.UI.Label label, int seed, int population) : BaseSystem +{ + private float _accumulated; + private int _frames; + + protected override void OnUpdateGroup() + { + _accumulated += context.Clock.UnscaledDeltaTime; + _frames++; + if (_accumulated < 0.25f) + { + return; + } + + var fps = _frames / _accumulated; + _accumulated = 0f; + _frames = 0; + label.Text = $"LittleSim | seed {seed} | население {population} | {fps:F0} FPS\nWASD — камера, колесо — зум, ` — консоль ('regen', 'timescale')"; + } +} diff --git a/src/LittleSim/Sim/WorldGenerator.cs b/src/LittleSim/Sim/WorldGenerator.cs new file mode 100644 index 0000000..e8d171c --- /dev/null +++ b/src/LittleSim/Sim/WorldGenerator.cs @@ -0,0 +1,112 @@ +using Microsoft.Xna.Framework; + +namespace LittleSim.Sim; + +/// Тип клетки мира по высоте рельефа. +public enum TerrainKind +{ + DeepWater, + Water, + Sand, + Grass, + Forest, + Mountain, +} + +/// +/// Детерминированная генерация рельефа: случайное поле, сглаженное несколькими проходами. +/// Один и тот же сид всегда даёт один и тот же мир — фундамент воспроизводимой симуляции. +/// +public static class WorldGenerator +{ + public static float[,] GenerateHeights(int width, int height, int seed) + { + var random = new Random(seed); + var heights = new float[width, height]; + for (var x = 0; x < width; x++) + { + for (var y = 0; y < height; y++) + { + heights[x, y] = random.NextSingle(); + } + } + + // Сглаживание усреднением окрестности 3×3 — дёшево и даёт связные "континенты". + for (var pass = 0; pass < 4; pass++) + { + var smoothed = new float[width, height]; + for (var x = 0; x < width; x++) + { + for (var y = 0; y < height; y++) + { + var sum = 0f; + var count = 0; + for (var dx = -1; dx <= 1; dx++) + { + for (var dy = -1; dy <= 1; dy++) + { + var nx = x + dx; + var ny = y + dy; + if (nx >= 0 && nx < width && ny >= 0 && ny < height) + { + sum += heights[nx, ny]; + count++; + } + } + } + + smoothed[x, y] = sum / count; + } + } + + heights = smoothed; + } + + Normalize(heights, width, height); + return heights; + } + + public static TerrainKind Classify(float height) => height switch + { + < 0.30f => TerrainKind.DeepWater, + < 0.42f => TerrainKind.Water, + < 0.48f => TerrainKind.Sand, + < 0.68f => TerrainKind.Grass, + < 0.85f => TerrainKind.Forest, + _ => TerrainKind.Mountain, + }; + + public static Color ColorOf(TerrainKind kind) => kind switch + { + TerrainKind.DeepWater => new Color(23, 63, 95), + TerrainKind.Water => new Color(32, 99, 155), + TerrainKind.Sand => new Color(237, 201, 120), + TerrainKind.Grass => new Color(90, 160, 70), + TerrainKind.Forest => new Color(44, 110, 50), + TerrainKind.Mountain => new Color(136, 132, 128), + _ => Color.Magenta, + }; + + public static bool IsLand(TerrainKind kind) => + kind is TerrainKind.Sand or TerrainKind.Grass or TerrainKind.Forest; + + private static void Normalize(float[,] heights, int width, int height) + { + var min = float.MaxValue; + var max = float.MinValue; + foreach (var value in heights) + { + min = Math.Min(min, value); + max = Math.Max(max, value); + } + + var range = Math.Max(0.0001f, max - min); + for (var x = 0; x < width; x++) + { + for (var y = 0; y < height; y++) + { + heights[x, y] = (heights[x, y] - min) / range; + } + } + } +}