better default agent props & speed & position numeric, not strings

This commit is contained in:
STEINNI
2026-06-21 12:08:25 +00:00
parent 54db203e86
commit 06a7868882
5 changed files with 106 additions and 9 deletions
+17 -3
View File
@@ -47,7 +47,13 @@ app.helpers.formBuilder = {
}
if(component){
component.classList.add('formbuilder-field')
component.value = this.getPathInObj(fieldsValues, propName) || fieldsObj[propName].default
const rawValue = this.getPathInObj(fieldsValues, propName) ?? fieldsObj[propName].default
if(fieldsObj[propName].type === 'number') {
const n = Number(rawValue)
component.value = Number.isFinite(n) ? n : 0
} else {
component.value = rawValue
}
fieldRow.append(component)
allFields.push(fieldRow)
}
@@ -77,6 +83,14 @@ app.helpers.formBuilder = {
return(target[parts[parts.length - 1]] )
},
#fieldValue(el) {
if(el?.type === 'number') {
const n = Number(el.value)
return(Number.isFinite(n) ? n : el.value)
}
return(el.value)
},
getFieldsValues(rootSel){
const result = {}
document.querySelectorAll(`${rootSel} .formbuilder-field`).forEach(el => {
@@ -89,14 +103,14 @@ app.helpers.formBuilder = {
}
target = target[key]
}
target[path[path.length - 1]] = el.value
target[path[path.length - 1]] = this.#fieldValue(el)
})
return(result)
},
getFieldValue(rootSel, name){
const comp = document.querySelector(`${rootSel} .formbuilder-field[name="${name}"]`)
if(comp) return(comp.value)
if(comp) return(this.#fieldValue(comp))
else return(null)
},
+9 -1
View File
@@ -43,7 +43,15 @@ class AgentsModel extends WindozModel {
async getDefaultProps(id){
const aprops = await this.getProperties(id)
const defaults={ position: { x:0, y:0, z:0 }, speed: { x:0, y:0, z:0 }}
for(const p in aprops) defaults[p] = aprops[p].default
for(const p in aprops) {
const prop = aprops[p]
if(prop?.type === 'number') {
const n = Number(prop.default)
defaults[p] = Number.isFinite(n) ? n : 0
} else {
defaults[p] = prop.default
}
}
return(defaults)
}
}
+18 -1
View File
@@ -38,14 +38,31 @@ class KeyframesModel extends WindozModel {
)
}
#normalizeAxisVector(vec) {
if(!vec || typeof(vec) !== 'object') return(null)
const axes = ['x', 'y', 'z']
const out = {}
for(const axis of axes) {
const n = Number(vec[axis])
if(!Number.isFinite(n)) return(null)
out[axis] = n
}
return(out)
}
async save(kfId, data) {
const kfData = Object.keys(data).map(aid => {
const { position, speed, ...storeValues} = data[aid].values
const gpsPosition = this.#normalizeAxisVector(position)
const gpsSpeed = this.#normalizeAxisVector(speed)
if(!gpsPosition || !gpsSpeed) {
throw(new Error(`Agent ${aid}: position and speed must be numeric vectors`))
}
return({
aid: aid,
type: data[aid].type,
storeValues: storeValues,
gpsValues: { position: data[aid].values.position, speed: data[aid].values.speed }
gpsValues: { position: gpsPosition, speed: gpsSpeed },
})
})
+3 -3
View File
@@ -207,14 +207,14 @@ class KeyframeView extends WindozDomContent {
if(this.currentlySelectedAid && this.kfArena.agents[this.currentlySelectedAid]){
const AgentValues = this.getFieldsValues('div[data-output="agentProperties"]')
this.kfArena.agents[this.currentlySelectedAid].values = AgentValues
const val = Number.parseInt(comp.value, 10)
if((comp.name.startsWith('position.')) && (!Number.isNaN(val))){
const val = Number(comp.value)
if((comp.name.startsWith('position.')) && (Number.isFinite(val))){
this.kfArena.moveAgent(this.currentlySelectedAid, {
x: this.getFieldValue('div[data-output="agentProperties"]', 'position.x'),
y: this.getFieldValue('div[data-output="agentProperties"]', 'position.y'),
z: this.getFieldValue('div[data-output="agentProperties"]', 'position.z'),
})
} else if((comp.name.startsWith('speed.')) && (!Number.isNaN(val))){
} else if((comp.name.startsWith('speed.')) && (Number.isFinite(val))){
this.kfArena.changeAgentSpeed(this.currentlySelectedAid, {
x: this.getFieldValue('div[data-output="agentProperties"]', 'speed.x'),
y: this.getFieldValue('div[data-output="agentProperties"]', 'speed.y'),
+59 -1
View File
@@ -36,4 +36,62 @@ The reason for this remark is to take provision for a future where I might want
{ "eventType": "remove", "payload": { }, "sender": "agent42" }
We need to think about how we start a new simulation from GPS point of view :
As we are in a distributed context, here is the workflow I'm thinking of :
1. Some orchestrator process will put all agents data in the arena Redis store.
2. Then it will start all agents processes, in a special mode "onYourMarks" where they can initialize, time is frozen to zero, any non initialization computation is forbidden.
3. When each agent is initialized, it fires an arena event "readyToStart"
4. When the orchestrator has accounted for all agents, it fires an "arenaStart" event, that unleashes all agents.
Now where is GPS in all of this :
GPS should behave like an agent: listen for "onYourMarks" event, initialize, send "readToStart" when done, and start time-ticking when it received a "bigBang" event.
Contrary to agents, GPS daemon itself is supposed to be running already.
So the initialization is mainly doing a first agents scan to fill its registry. (with an unvalued T0 and collision-ticks not running). Scanning all agents means going into redis store to get their vector (its only after this step than GPS become the positions authority)
Upon receiving "bigBang", T0 is valued, and the collisionsTicks starts.
That's the general idea.
Please check if you don't see incoherence, gap or edge case.
5. GPS time communicated to agents is simulation time, thus with T(bigBang) = 0
Therefore, when the orchestrator send a "bigBang" event, GPS starts general scan
and does the pre-compute with all positions at T=0
For eventual time-elapsed computations internal to GPS, it can keep a reference, say bigBangEpoch to epoch (miliseconds)
at the moment he receives the "bigBang" event.
From then on, current Simulation time is now() - bigBangEpoch
Next step: the embrio of simOrchestrator.
Can you create a second GodDaemon called like that, aside GPS, that uses the same config, the same general pattern, adapted start & stop scripts,
So, the first thing you must understand, is that all busses, thus all meshes and all redisConnexions can carry both types of messages :
1. Request-reply: action request (possibly with reqid) requestor to provider. After action completion (or rejection), the provider answers the requestor (private chan replacing {uuid} with sender) with a reply (on the same reqid if present).
The payload must contain "action", with an imperative form.
Action must be verified to arrive on a specific chan, and the sender be allowed by access-rights.
2. Events broadcasting: not a request, just a message for anybody interested (so no "rejection" per se, but eventual ignore). No answer, but eventual local processing which might or not endup firing other event(s).
The payload must contain "eventType", with a generally a past tense. (like "he guys, I UPDATED my color")
Event routing combines chan & eventType (for example, and "updated" eventType could appear on a agentColor chan, and on a agentHealth chan, with different meanings and thus handling)
These two types can happen anywhere, it is not a "per mesh drift".
Probably, most Actions will concentrate in the SYSTEM mesh, because the management by the front-end and API is mostly user driven (user action becomes a request on the system bus).
Probably, the ARENA will be mostly event-driven, because agents have no overview of the arena (so to who ask for what?)
Nevertheless, there will be many cases where this is not true:
GODs emitting events that other gods or even the Front-End should be aware of.
The observer feeding the GUI on arena changes in an event-driven way.
Agent in the Arena requesting a service from a known god. (like "Hey GPS, give me my latest vector")
And maybe even someday clever agents that provide actions to other agents in their proximity for example.
"Action" folder was originally meant for...actions !
Now we need to find a clean, elegant, universal (across daemons & across meshes)
way of coexist both actions and events.
Describe a proposal that stays close to what we have, but separates events from actions.