simz 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
simz-0.1.0/.gitignore ADDED
@@ -0,0 +1,175 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # ruff
155
+ .ruff_cache/
156
+
157
+ # Pyre type checker
158
+ .pyre/
159
+
160
+ # pytype static type analyzer
161
+ .pytype/
162
+
163
+ # Cython debug symbols
164
+ cython_debug/
165
+
166
+ # PyCharm
167
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
168
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
169
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
170
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
171
+ #.idea/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+ dist/
simz-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: simz
3
+ Version: 0.1.0
4
+ Summary: A lightweight ECS-based simulation engine
5
+ Project-URL: Repository, https://github.com/danield137/projectzero
6
+ Author-email: Daniel Dror <danield137@gmail.com>
7
+ License: Copyright (c) [Daniel Dror;danield137@gmail.com]
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software for non-commercial, non-profit purposes only.
9
+ Commercial use, including but not limited to use by for-profit organizations or individuals for business purposes, is not permitted without obtaining a separate commercial license from the copyright holder.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY...
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Games/Entertainment :: Simulation
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: numpy>=2.2.4
18
+ Requires-Dist: termcolor>=2.5.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # sim
22
+
23
+ A lightweight ECS (Entity Component System) based simulation engine.
24
+
25
+ ## Features
26
+
27
+ - **ECS Core** β€” Entity-Component-System architecture with generational IDs and efficient queries
28
+ - **AI Module** β€” Brain/context/memory abstractions for agent behavior
29
+ - **Common Utilities** β€” Math helpers, logging, data structures (generational containers, running stats)
30
+ - **Configurable** β€” Runtime configuration for simulation parameters
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install simz
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from sim.ecs.core import ECS
42
+ from sim.ecs.component import Component
43
+ from sim.ecs.system import System
44
+
45
+ # Define components
46
+ class Position(Component):
47
+ def __init__(self, x: float, y: float):
48
+ self.x = x
49
+ self.y = y
50
+
51
+ # Create ECS and entities
52
+ ecs = ECS()
53
+ ecs.create_entity("player", [Position(0, 0)])
54
+ ```
55
+
56
+ ## License
57
+
58
+ Non-commercial use only. See [LICENSE](LICENSE) for details.
simz-0.1.0/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # sim
2
+
3
+ A lightweight ECS (Entity Component System) based simulation engine.
4
+
5
+ ## Features
6
+
7
+ - **ECS Core** β€” Entity-Component-System architecture with generational IDs and efficient queries
8
+ - **AI Module** β€” Brain/context/memory abstractions for agent behavior
9
+ - **Common Utilities** β€” Math helpers, logging, data structures (generational containers, running stats)
10
+ - **Configurable** β€” Runtime configuration for simulation parameters
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pip install simz
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```python
21
+ from sim.ecs.core import ECS
22
+ from sim.ecs.component import Component
23
+ from sim.ecs.system import System
24
+
25
+ # Define components
26
+ class Position(Component):
27
+ def __init__(self, x: float, y: float):
28
+ self.x = x
29
+ self.y = y
30
+
31
+ # Create ECS and entities
32
+ ecs = ECS()
33
+ ecs.create_entity("player", [Position(0, 0)])
34
+ ```
35
+
36
+ ## License
37
+
38
+ Non-commercial use only. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "simz"
3
+ version = "0.1.0"
4
+ description = "A lightweight ECS-based simulation engine"
5
+ authors = [
6
+ {name = "Daniel Dror", email = "danield137@gmail.com"},
7
+ ]
8
+ dependencies = [
9
+ "numpy>=2.2.4",
10
+ "termcolor>=2.5.0",
11
+ ]
12
+ requires-python = ">=3.10"
13
+ readme = "README.md"
14
+ license = {text = """
15
+ Copyright (c) [Daniel Dror;danield137@gmail.com]
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software for non-commercial, non-profit purposes only.
17
+ Commercial use, including but not limited to use by for-profit organizations or individuals for business purposes, is not permitted without obtaining a separate commercial license from the copyright holder.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY...
20
+ """}
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Developers",
24
+ "Programming Language :: Python :: 3",
25
+ "Topic :: Games/Entertainment :: Simulation",
26
+ ]
27
+
28
+ [project.urls]
29
+ Repository = "https://github.com/danield137/projectzero"
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/sim"]
File without changes
@@ -0,0 +1,10 @@
1
+ import abc
2
+ import functools
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True, slots=True)
7
+ class ActionStep(abc.ABC):
8
+ @functools.cached_property
9
+ def name(self):
10
+ return self.__class__.__qualname__
@@ -0,0 +1,29 @@
1
+ import abc
2
+ import enum
3
+ from dataclasses import dataclass
4
+ from typing import Generic, TypeVar
5
+
6
+ from sim.ai import ActionStep
7
+ from sim.ai.context import BrainContext
8
+ from sim.ai.memory import Memory
9
+
10
+ TGoal = TypeVar("TGoal", bound=enum.Enum)
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class GoalSelector(abc.ABC, Generic[TGoal]):
15
+ @abc.abstractmethod
16
+ def select_goal(self, ctx: BrainContext) -> TGoal: ...
17
+
18
+
19
+ @dataclass(slots=True)
20
+ class Planner(abc.ABC, Generic[TGoal]):
21
+ @abc.abstractmethod
22
+ def make_plan(self, ctx: BrainContext, goal: str) -> list[ActionStep]: ...
23
+
24
+
25
+ @dataclass(slots=True)
26
+ class Brain(Generic[TGoal]):
27
+ goal_selector: GoalSelector[TGoal]
28
+ planner: Planner[TGoal]
29
+ memory: Memory
@@ -0,0 +1,18 @@
1
+ from dataclasses import dataclass
2
+
3
+ from sim.ai.memory import Memory, MemoryData
4
+
5
+
6
+ @dataclass(slots=True, frozen=True)
7
+ class BrainContext:
8
+ """
9
+ Context for the AI brain, providing necessary data for decision-making.
10
+ All "sensory" data is normalized to a range of 0.0 to 1.0.
11
+ This context is used by the AI engine to make decisions and plans.
12
+ """
13
+
14
+ simulation_time: int
15
+ eid: int
16
+ etype: str
17
+ memory_data: MemoryData
18
+ memory_engine: Memory
@@ -0,0 +1,230 @@
1
+ import abc
2
+ import enum
3
+ import heapq
4
+ import math
5
+ import random
6
+ from collections.abc import Callable, Iterator
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+ from sim.common.math import Vector, cosine
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class MemoryFact:
15
+ """
16
+ *uid* : unique and stable identifier
17
+ *t0* : tick when the fact was first encoded
18
+ *value*: **opaque payload** (Memory never inspects this)
19
+ *ctx* : optional context vector for similarity search
20
+ """
21
+
22
+ uid: str
23
+ tag: str
24
+ t0: int
25
+ value: Any
26
+ ctx: Vector | None = field(init=False, default=None)
27
+
28
+
29
+ class MemoryType(str, enum.Enum):
30
+ PERFECT = "PERFECT"
31
+ IMPERFECT = "IMPERFECT"
32
+
33
+
34
+ @dataclass(slots=True)
35
+ class MemoryData:
36
+ ltm: dict[str, MemoryFact] = field(default_factory=dict[str, MemoryFact])
37
+ stm: list[MemoryFact] = field(default_factory=list[MemoryFact])
38
+ strength: dict[str, float] = field(default_factory=dict[str, float])
39
+ cue: dict[str, set[str]] = field(default_factory=dict[str, set[str]])
40
+ rng_state: int = field(default=0xDEADBEEF) # splitmix state
41
+
42
+ def all(self) -> Iterator[MemoryFact]:
43
+ if self.stm:
44
+ yield from self.stm
45
+ if self.ltm.values():
46
+ yield from self.ltm.values()
47
+
48
+ def exists(self, uid: str) -> bool:
49
+ """Check if a fact with the given UID exists in either STM or LTM."""
50
+ return uid in self.ltm or any(fact.uid == uid for fact in self.stm)
51
+
52
+
53
+ @dataclass(slots=True, frozen=True)
54
+ class MemQuery:
55
+ fact_type: type[MemoryFact] | None = None
56
+ uid: str | None = None
57
+ where: Callable[[MemoryFact], bool] | None = None
58
+ ctx: Vector | None = None # current context for activation boost
59
+ k: int = 5
60
+
61
+ @staticmethod
62
+ def tag_eq(tag: str) -> "MemQuery":
63
+ """Create a query for a specific tag."""
64
+ return MemQuery(where=lambda f: f.tag == tag)
65
+
66
+
67
+ @dataclass(slots=True)
68
+ class Memory(abc.ABC):
69
+ @abc.abstractmethod
70
+ def remember(self, md: MemoryData, fact: MemoryFact) -> None: ...
71
+
72
+ @abc.abstractmethod
73
+ def recall(self, md: MemoryData, q: MemQuery, now: int) -> list[MemoryFact]: ...
74
+
75
+ @abc.abstractmethod
76
+ def tick(self, md: MemoryData, dt: float, now: int) -> None: ...
77
+
78
+ @abc.abstractmethod
79
+ def forget(self, md: MemoryData, uid: str) -> None: ...
80
+
81
+
82
+ # ———– PerfectMemory β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
83
+ class PerfectMemory(Memory):
84
+ def remember(self, md: MemoryData, fact: MemoryFact) -> None:
85
+ md.ltm[fact.uid] = fact
86
+
87
+ def recall(self, md: MemoryData, q: MemQuery, now: int) -> list[MemoryFact]:
88
+ cand = (fact for facts in (md.ltm.values(), md.stm) for fact in facts)
89
+ if q.where:
90
+ cand = filter(q.where, cand)
91
+ return list(cand)[: q.k]
92
+
93
+ def forget(self, md: MemoryData, uid: str) -> None:
94
+ """Forget a fact by UID."""
95
+ md.ltm.pop(uid, None)
96
+ md.stm = [f for f in md.stm if f.uid != uid]
97
+ for s in md.cue.values():
98
+ s.discard(uid)
99
+
100
+ def tick(self, md: MemoryData, dt: float, now: int) -> None:
101
+ # no‐op for perfect memory
102
+ return
103
+
104
+
105
+ # ———– ImperfectMemory knobs & logic β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
106
+ class ImperfectMemory(Memory):
107
+ ENCODE_NOISE_P = 0.10
108
+ STM_SPAN_TICKS = 300
109
+ INTERFERENCE_P = 0.40
110
+ DECAY_HALF_LIFE = 30_000
111
+ RETRIEVAL_NOISE_SD = 0.25
112
+
113
+ def remember(self, md: MemoryData, fact: MemoryFact) -> None:
114
+ rng = random.Random(md.rng_state)
115
+ if rng.random() < self.ENCODE_NOISE_P:
116
+ # fact = {**fact, "noisy": True} # your distortion
117
+ ...
118
+ md.stm.append(fact)
119
+ md.rng_state = rng.getrandbits(64)
120
+
121
+ def tick(self, md: MemoryData, dt: float, now: int) -> None:
122
+ # STM β†’ LTM + interference
123
+ while md.stm and now - md.stm[0].t0 >= self.STM_SPAN_TICKS:
124
+ f = md.stm.pop(0)
125
+ bucket = f.uid.split("-", 1)[0]
126
+ md.cue.setdefault(bucket, set()).add(f.uid)
127
+ md.ltm[f.uid] = f
128
+ md.strength[f.uid] = md.strength.get(f.uid, 0.01) + 1.0
129
+
130
+ # decay
131
+ k = 0.5 ** (dt / self.DECAY_HALF_LIFE)
132
+ dead: list[str] = []
133
+ for uid, s in md.strength.items():
134
+ s *= k
135
+ if s < 0.02:
136
+ dead.append(uid)
137
+ md.strength[uid] = s
138
+ for uid in dead:
139
+ md.ltm.pop(uid, None)
140
+ for s in md.cue.values():
141
+ s.discard(uid)
142
+
143
+ def recall(self, md: MemoryData, q: MemQuery, now: int) -> list[MemoryFact]:
144
+ rng = random.Random(md.rng_state)
145
+ result_heap: list[
146
+ tuple[float, MemoryFact]
147
+ ] = [] # Will store (-score, fact) pairs for min-heap behavior with max scores
148
+
149
+ # Process candidates from both LTM and STM without materializing full lists
150
+ def process_fact(f: MemoryFact):
151
+ if q.where and not q.where(f):
152
+ return
153
+
154
+ base = md.strength.get(f.uid, 0.01)
155
+ act = math.log(base / (now - f.t0 or 1e-9))
156
+ sim = 0.0
157
+ if q.ctx and f.ctx is not None:
158
+ sim = cosine(q.ctx, f.ctx)
159
+ noise = rng.gauss(0.0, self.RETRIEVAL_NOISE_SD)
160
+ score = act + sim + noise
161
+
162
+ # Use negative score for min-heap to function as max-heap
163
+ if len(result_heap) < q.k:
164
+ heapq.heappush(result_heap, (-score, f))
165
+ elif -score > result_heap[0][0]:
166
+ heapq.heappushpop(result_heap, (-score, f))
167
+
168
+ # Process long-term memory
169
+ for f in md.ltm.values():
170
+ process_fact(f)
171
+
172
+ # Process short-term memory
173
+ for f in md.stm:
174
+ process_fact(f)
175
+
176
+ md.rng_state = rng.getrandbits(64)
177
+
178
+ # Extract results in descending order of score
179
+ results = [f for _, f in sorted(result_heap, key=lambda x: x[0])]
180
+ return results
181
+
182
+ def forget(self, md: MemoryData, uid: str) -> None:
183
+ """Forget a fact by UID."""
184
+ md.ltm.pop(uid, None)
185
+ md.stm = [f for f in md.stm if f.uid != uid]
186
+ for s in md.cue.values():
187
+ s.discard(uid)
188
+ md.strength.pop(uid, None)
189
+
190
+
191
+ # shared singleton instances
192
+ _perfect_memory = PerfectMemory()
193
+ _imperfect_memory = ImperfectMemory()
194
+
195
+
196
+ def get_memory(memory_type: MemoryType) -> Memory:
197
+ if memory_type == MemoryType.PERFECT:
198
+ return _perfect_memory
199
+ return _imperfect_memory
200
+
201
+
202
+ # ———– faΓ§ade dispatchers β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
203
+
204
+
205
+ class MemWriter:
206
+ @staticmethod
207
+ def write(md: MemoryData, memory_type: str, fact: MemoryFact) -> None:
208
+ if memory_type == MemoryType.PERFECT:
209
+ _perfect_memory.remember(md, fact)
210
+ elif memory_type == MemoryType.IMPERFECT:
211
+ _imperfect_memory.remember(md, fact)
212
+
213
+
214
+ class MemReader:
215
+ @staticmethod
216
+ def read(md: MemoryData, memory_type: str, q: MemQuery, now: int) -> list[MemoryFact]:
217
+ if memory_type == MemoryType.PERFECT:
218
+ return _perfect_memory.recall(md, q, now)
219
+ if memory_type == MemoryType.IMPERFECT:
220
+ return _imperfect_memory.recall(md, q, now)
221
+ return [] # empty list if memory type is unknown
222
+
223
+
224
+ class MemHousekeeping:
225
+ @staticmethod
226
+ def tick(md: MemoryData, memory_type: str, dt: float, now: int) -> None:
227
+ if memory_type == MemoryType.PERFECT:
228
+ _perfect_memory.tick(md, dt, now)
229
+ else:
230
+ _imperfect_memory.tick(md, dt, now)
File without changes
File without changes